From: Jaromír Mikeš Date: Tue, 15 Aug 2017 02:22:07 +0000 (+0200) Subject: New upstream version 0.14.2~dfsg1 X-Git-Tag: archive/raspbian/0.15.4+ds1-1+rpi1^2~12^2~9 X-Git-Url: https://dgit.raspbian.org/%22http://www.example.com/cgi/%22/%22http:/www.example.com/cgi/%22?a=commitdiff_plain;h=750aef8844e7ec67c0744796420780ef5876002b;p=giada.git New upstream version 0.14.2~dfsg1 --- diff --git a/ChangeLog b/ChangeLog index d2e1595..f9e8033 100644 --- a/ChangeLog +++ b/ChangeLog @@ -12,6 +12,22 @@ -------------------------------------------------------------------------------- +0.14.2 --- 2017 . 08 . 14 +- [Sample Editor] Audible preview (with optional loop mode) +- [Sample Editor] Frame-precise editing +- [Sample Editor] Show sample's information +- [Sample Editor] Improved fade out algorithm +- [Sample Editor] Process both left and right channel's data while drawing +- Better Wave objects handling +- Improved channels' memory management +- Improved empty columns cleanup algorithm +- Update Catch version +- Update JUCE version (5.1.1) +- Update Jansson version (2.10) +- Fix missing tempo update on reset to init state +- Fix wrong memory allocation for UI-less plugins + + 0.14.1 --- 2017 . 07 . 16 - Update JUCE library to 5.0.2 - Show play head in Sample Editor diff --git a/Makefile.am b/Makefile.am index 0c0e2da..6bdf82b 100644 --- a/Makefile.am +++ b/Makefile.am @@ -45,6 +45,8 @@ src/core/storager.h \ src/core/storager.cpp \ src/core/clock.h \ src/core/clock.cpp \ +src/core/waveManager.h \ +src/core/waveManager.cpp \ src/glue/main.h \ src/glue/main.cpp \ src/glue/io.h \ @@ -279,9 +281,8 @@ endif if LINUX giada_SOURCES += src/deps/rtaudio-mod/RtAudio.h src/deps/rtaudio-mod/RtAudio.cpp -# -Wno-error=misleading-indentation: don't stop on JUCE warnings on GCC6 # -Wno-error=unused-function: don't stop on JUCE's unused functions -giada_CXXFLAGS += -Wno-error=misleading-indentation -Wno-error=unused-function +giada_CXXFLAGS += -Wno-error=unused-function giada_CPPFLAGS += -D__LINUX_ALSA__ -D__LINUX_PULSE__ -D__UNIX_JACK__ giada_LDADD = -lsndfile -lfltk -lXext -lX11 -lXft -lXpm -lm -ljack -lasound \ -lpthread -ldl -lpulse-simple -lpulse -lsamplerate -lrtmidi -ljansson \ @@ -338,13 +339,17 @@ giada_tests_SOURCES = \ tests/main.cpp \ tests/conf.cpp \ tests/wave.cpp \ +tests/waveManager.cpp \ tests/patch.cpp \ tests/midiMapConf.cpp \ tests/pluginHost.cpp \ tests/utils.cpp \ tests/recorder.cpp \ +tests/waveFx.cpp \ src/core/conf.cpp \ src/core/wave.cpp \ +src/core/waveManager.cpp \ +src/core/waveFx.cpp \ src/core/midiMapConf.cpp \ src/core/patch.cpp \ src/core/plugin.cpp \ diff --git a/src/core/channel.cpp b/src/core/channel.cpp index f519e4a..5677f32 100644 --- a/src/core/channel.cpp +++ b/src/core/channel.cpp @@ -53,6 +53,7 @@ Channel::Channel(int type, int status, int bufferSize) : bufferSize (bufferSize), midiFilter (-1), pan (0.5f), + previewMode (G_PREVIEW_NONE), type (type), status (status), key (0), @@ -82,10 +83,6 @@ Channel::Channel(int type, int status, int bufferSize) midiOutLmute (0x0), midiOutLsolo (0x0) { - vChan = (float *) malloc(bufferSize * sizeof(float)); - if (!vChan) - gu_log("[Channel::allocVchan] unable to alloc memory for vChan\n"); - std::memset(vChan, 0, bufferSize * sizeof(float)); } @@ -95,8 +92,23 @@ Channel::Channel(int type, int status, int bufferSize) Channel::~Channel() { status = STATUS_OFF; - if (vChan) - free(vChan); + if (vChan != nullptr) + delete[] vChan; +} + + +/* -------------------------------------------------------------------------- */ + + +bool Channel::allocBuffers() +{ + vChan = new (std::nothrow) float[bufferSize]; + if (vChan == nullptr) { + gu_log("[Channel::allocBuffers] unable to alloc memory for vChan!\n"); + return false; + } + std::memset(vChan, 0, bufferSize * sizeof(float)); + return true; } @@ -401,6 +413,21 @@ float Channel::calcPanning(int ch) /* -------------------------------------------------------------------------- */ +void Channel::setPreviewMode(int m) +{ + previewMode = m; +} + + +bool Channel::isPreview() +{ + return previewMode != G_PREVIEW_NONE; +} + + +/* -------------------------------------------------------------------------- */ + + #ifdef WITH_VST juce::MidiBuffer &Channel::getPluginMidiEvents() diff --git a/src/core/channel.h b/src/core/channel.h index 96491ad..6f96257 100644 --- a/src/core/channel.h +++ b/src/core/channel.h @@ -72,11 +72,16 @@ protected: float pan; + /* previewMode + Whether the channel is in audio preview mode or not. */ + + int previewMode; + /* sendMidiLMessage Composes a MIDI message by merging bytes from MidiMap conf class, and sends it to KernelMidi. */ - void sendMidiLmessage(uint32_t learn, const giada::m::midimap::message_t &msg); + void sendMidiLmessage(uint32_t learn, const giada::m::midimap::message_t& msg); /* calcPanning Given an audio channel (stereo: 0 or 1) computes the current panning value. */ @@ -92,19 +97,25 @@ public: /* copy * Make a shallow copy (no vChan/pChan allocation) of another channel. */ - virtual void copy(const Channel *src, pthread_mutex_t *pluginMutex) = 0; + virtual void copy(const Channel* src, pthread_mutex_t* pluginMutex) = 0; /* readPatch * Fill channel with data from patch. */ - virtual int readPatch(const std::string &basePath, int i, - pthread_mutex_t *pluginMutex, int samplerate, int rsmpQuality); + virtual int readPatch(const std::string& basePath, int i, + pthread_mutex_t* pluginMutex, int samplerate, int rsmpQuality); /* process Merges vChannels into buffer, plus plugin processing (if any). Warning: inBuffer might be nullptr if no input devices are available for recording. */ - virtual void process(float *outBuffer, float *inBuffer) = 0; + virtual void process(float* outBuffer, float* inBuffer) = 0; + + /* Preview + Makes itself audibile for audio preview, such as Sample Editor or other + tools. */ + + virtual void preview(float* outBuffer) = 0; /* start Action to do when channel starts. doQuantize = false (don't quantize) @@ -167,7 +178,7 @@ public: // TODO - quantize is useless! - virtual void parseAction(giada::m::recorder::action *a, int localFrame, + virtual void parseAction(giada::m::recorder::action* a, int localFrame, int globalFrame, int quantize, bool mixerIsRunning) = 0; /* rewind @@ -198,25 +209,31 @@ public: virtual void receiveMidi(uint32_t msg); + /* allocBuffers + Mandatory method to allocate memory for internal buffers. Call it after the + object has been constructed. */ + + virtual bool allocBuffers(); + /* ------------------------------------------------------------------------ */ - int index; // unique id - int type; // midi or sample - int status; // status: see const.h - int key; // keyboard button - float volume; // global volume - float volume_i; // internal volume - float volume_d; // delta volume (for envelope) - bool mute_i; // internal mute - bool mute_s; // previous mute status after being solo'd - bool mute; // global mute - bool solo; - bool hasActions; // has something recorded - bool readActions; // read what's recorded - bool armed; // armed for recording - int recStatus; // status of recordings (waiting, ending, ...) - float *vChan; // virtual channel - geChannel *guiChannel; // pointer to a gChannel object, part of the GUI + int index; // unique id + int type; // midi or sample + int status; // status: see const.h + int key; // keyboard button + float volume; // global volume + float volume_i; // internal volume + float volume_d; // delta volume (for envelope) + bool mute_i; // internal mute + bool mute_s; // previous mute status after being solo'd + bool mute; // global mute + bool solo; + bool hasActions; // has something recorded + bool readActions; // read what's recorded + bool armed; // armed for recording + int recStatus; // status of recordings (waiting, ending, ...) + float* vChan; // virtual channel + geChannel* guiChannel; // pointer to a gChannel object, part of the GUI // TODO - midi structs, please @@ -240,7 +257,7 @@ public: uint32_t midiOutLsolo; #ifdef WITH_VST - std::vector plugins; + std::vector plugins; #endif @@ -261,13 +278,16 @@ public: void setPan(float v); float getPan(); + void setPreviewMode(int m); + bool isPreview(); + #ifdef WITH_VST /* getPluginMidiEvents * Return a reference to midiBuffer stack. This is available for any kind of * channel, but it makes sense only for MIDI channels. */ - juce::MidiBuffer &getPluginMidiEvents(); + juce::MidiBuffer& getPluginMidiEvents(); void clearMidiBuffer(); diff --git a/src/core/clock.cpp b/src/core/clock.cpp index fce225a..1a7e5d5 100644 --- a/src/core/clock.cpp +++ b/src/core/clock.cpp @@ -60,7 +60,7 @@ int midiTCseconds = 0; int midiTCminutes = 0; int midiTChours = 0; -#ifdef __linux__ +#ifdef G_OS_LINUX kernelAudio::JackState jackStatePrev; #endif @@ -84,6 +84,11 @@ void updateQuanto() void init(int sampleRate, float midiTCfps) { midiTCrate = (sampleRate / midiTCfps) * 2; // stereo values + running = false; + bpm = G_DEFAULT_BPM; + bars = G_DEFAULT_BARS; + beats = G_DEFAULT_BEATS; + quantize = G_DEFAULT_QUANTIZE; updateFrameBars(); } @@ -339,7 +344,7 @@ void sendMIDIrewind() /* -------------------------------------------------------------------------- */ -#ifdef __linux__ +#ifdef G_OS_LINUX void recvJackSync() { diff --git a/src/core/conf.cpp b/src/core/conf.cpp index 8cfe0f9..39bc9ec 100644 --- a/src/core/conf.cpp +++ b/src/core/conf.cpp @@ -58,7 +58,7 @@ void sanitize() if (soundDeviceIn < -1) soundDeviceIn = G_DEFAULT_SOUNDDEV_IN; if (channelsOut < 0) channelsOut = 0; if (channelsIn < 0) channelsIn = 0; - if (buffersize < 8 || buffersize > 4096) buffersize = G_DEFAULT_BUFSIZE; + if (buffersize < G_MIN_BUF_SIZE || buffersize > G_MAX_BUF_SIZE) buffersize = G_DEFAULT_BUFSIZE; if (delayComp < 0) delayComp = G_DEFAULT_DELAYCOMP; if (midiPortOut < -1) midiPortOut = G_DEFAULT_MIDI_SYSTEM; if (midiPortOut < -1) midiPortOut = G_DEFAULT_MIDI_PORT_OUT; @@ -72,14 +72,14 @@ void sanitize() if (actionEditorW < 640) actionEditorW = 640; if (actionEditorH < 176) actionEditorH = 176; if (actionEditorZoom < 100) actionEditorZoom = 100; - if (actionEditorGridVal < 0) actionEditorGridVal = 0; + if (actionEditorGridVal < 0 || actionEditorGridVal > G_MAX_GRID_VAL) actionEditorGridVal = 0; if (actionEditorGridOn < 0) actionEditorGridOn = 0; if (pianoRollH <= 0) pianoRollH = 422; if (sampleEditorX < 0) sampleEditorX = 0; if (sampleEditorY < 0) sampleEditorY = 0; if (sampleEditorW < 500) sampleEditorW = 500; if (sampleEditorH < 292) sampleEditorH = 292; - if (sampleEditorGridVal < 0) sampleEditorGridVal = 0; + if (sampleEditorGridVal < 0 || sampleEditorGridVal > G_MAX_GRID_VAL) sampleEditorGridVal = 0; if (sampleEditorGridOn < 0) sampleEditorGridOn = 0; if (midiInputX < 0) midiInputX = 0; if (midiInputY < 0) midiInputY = 0; @@ -212,7 +212,7 @@ int sampleEditorX = 0; int sampleEditorY = 0; int sampleEditorW = 640; int sampleEditorH = 480; -int sampleEditorGridVal = 1; +int sampleEditorGridVal = 0; int sampleEditorGridOn = false; int midiInputX = 0; diff --git a/src/core/const.h b/src/core/const.h index 63c6a53..ad5384a 100644 --- a/src/core/const.h +++ b/src/core/const.h @@ -46,10 +46,10 @@ /* -- version --------------------------------------------------------------- */ #define G_APP_NAME "Giada" -#define G_VERSION_STR "0.14.1" +#define G_VERSION_STR "0.14.2" #define G_VERSION_MAJOR 0 #define G_VERSION_MINOR 14 -#define G_VERSION_PATCH 1 +#define G_VERSION_PATCH 2 #define CONF_FILENAME "giada.conf" @@ -98,6 +98,9 @@ #define G_MIN_COLUMN_WIDTH 140 #define G_MAX_BOOST_DB 20.0f #define G_MAX_PITCH 4.0f +#define G_MAX_GRID_VAL 64 +#define G_MIN_BUF_SIZE 8 +#define G_MAX_BUF_SIZE 4096 @@ -130,26 +133,28 @@ #endif #define G_DEFAULT_SOUNDDEV_OUT 0 // FIXME - please override with rtAudio::getDefaultDevice (or similar) -#define G_DEFAULT_SOUNDDEV_IN -1 // no recording by default: input disabled +#define G_DEFAULT_SOUNDDEV_IN -1 // no recording by default: input disabled #define G_DEFAULT_MIDI_SYSTEM 0 #define G_DEFAULT_MIDI_PORT_IN -1 #define G_DEFAULT_MIDI_PORT_OUT -1 #define G_DEFAULT_SAMPLERATE 44100 #define G_DEFAULT_BUFSIZE 1024 #define G_DEFAULT_DELAYCOMP 0 -#define G_DEFAULT_VOL 1.0f -#define G_DEFAULT_PITCH 1.0f -#define G_DEFAULT_BOOST 1.0f -#define G_DEFAULT_OUT_VOL 1.0f -#define G_DEFAULT_IN_VOL 1.0f -#define G_DEFAULT_CHANMODE SINGLE_BASIC -#define G_DEFAULT_BPM 120.0f -#define G_DEFAULT_BEATS 4 -#define G_DEFAULT_BARS 1 -#define G_DEFAULT_QUANTIZE 0 // quantizer off +#define G_DEFAULT_BIT_DEPTH 32 // float +#define G_DEFAULT_AUDIO_CHANS 2 // stereo for internal processing +#define G_DEFAULT_VOL 1.0f +#define G_DEFAULT_PITCH 1.0f +#define G_DEFAULT_BOOST 1.0f +#define G_DEFAULT_OUT_VOL 1.0f +#define G_DEFAULT_IN_VOL 1.0f +#define G_DEFAULT_CHANMODE SINGLE_BASIC +#define G_DEFAULT_BPM 120.0f +#define G_DEFAULT_BEATS 4 +#define G_DEFAULT_BARS 1 +#define G_DEFAULT_QUANTIZE 0 // quantizer off #define G_DEFAULT_FADEOUT_STEP 0.01f // micro-fadeout speed #define G_DEFAULT_COLUMN_WIDTH 380 -#define G_DEFAULT_PATCH_NAME "(default patch)" +#define G_DEFAULT_PATCH_NAME "(default patch)" #define G_DEFAULT_MIDI_INPUT_UI_W 300 #define G_DEFAULT_MIDI_INPUT_UI_H 350 @@ -183,6 +188,13 @@ +/* -- preview modes --------------------------------------------------------- */ +#define G_PREVIEW_NONE 0x00 +#define G_PREVIEW_NORMAL 0x01 +#define G_PREVIEW_LOOP 0x02 + + + /* -- actions --------------------------------------------------------------- */ #define G_ACTION_KEYPRESS 0x01 // 0000 0001 #define G_ACTION_KEYREL 0x02 // 0000 0010 @@ -200,18 +212,15 @@ -/* -- mixerHandler signals -------------------------------------------------- */ -#define SAMPLE_LOADED_OK 0x01 -#define SAMPLE_LEFT_EMPTY 0x02 -#define SAMPLE_NOT_VALID 0x04 -#define SAMPLE_MULTICHANNEL 0x08 -#define SAMPLE_WRONG_BIT 0x10 -#define SAMPLE_WRONG_ENDIAN 0x20 -#define SAMPLE_WRONG_FORMAT 0x40 -#define SAMPLE_READ_ERROR 0x80 -#define SAMPLE_PATH_TOO_LONG 0x100 - -/** FIXME - add to SAMPLE_ series those for when exporting */ +/* -- responses and return codes -------------------------------------------- */ +#define G_RES_ERR_PROCESSING -6 +#define G_RES_ERR_WRONG_DATA -5 +#define G_RES_ERR_NO_DATA -4 +#define G_RES_ERR_PATH_TOO_LONG -3 +#define G_RES_ERR_IO -2 +#define G_RES_ERR_MEMORY -1 +#define G_RES_ERR 0 +#define G_RES_OK 1 diff --git a/src/core/midiChannel.cpp b/src/core/midiChannel.cpp index 0349ac6..fe58e7f 100644 --- a/src/core/midiChannel.cpp +++ b/src/core/midiChannel.cpp @@ -183,6 +183,15 @@ void MidiChannel::process(float *outBuffer, float *inBuffer) /* -------------------------------------------------------------------------- */ +void MidiChannel::preview(float *outBuffer) +{ + // No preview for MIDI channels (for now). +} + + +/* -------------------------------------------------------------------------- */ + + void MidiChannel::start(int frame, bool doQuantize, int quantize, bool mixerIsRunning, bool forceStart, bool isUserGenerated) { @@ -243,7 +252,7 @@ int MidiChannel::readPatch(const string &basePath, int i, midiOut = pch->midiOut; midiOutChan = pch->midiOutChan; - return SAMPLE_LOADED_OK; /// TODO - change name, it's meaningless here + return G_RES_OK; } diff --git a/src/core/midiChannel.h b/src/core/midiChannel.h index 3d938e2..feec2b1 100644 --- a/src/core/midiChannel.h +++ b/src/core/midiChannel.h @@ -52,6 +52,7 @@ public: void copy(const Channel *src, pthread_mutex_t *pluginMutex) override; void clear() override; void process(float *outBuffer, float *inBuffer) override; + void preview(float *outBuffer) override; void start(int frame, bool doQuantize, int quantize, bool mixerIsRunning, bool forceStart, bool isUserGenerated) override; void kill(int frame) override; diff --git a/src/core/midiMapConf.cpp b/src/core/midiMapConf.cpp index 8b70f14..5d28774 100644 --- a/src/core/midiMapConf.cpp +++ b/src/core/midiMapConf.cpp @@ -57,7 +57,7 @@ bool readInitCommands(json_t *jContainer) json_t *jInitCommand; json_array_foreach(jInitCommands, commandIndex, jInitCommand) { - string indexStr = "init command " + gu_itoa(commandIndex); + string indexStr = "init command " + gu_toString(commandIndex); if (!storager::checkObject(jInitCommand, indexStr.c_str())) return 0; diff --git a/src/core/mixer.cpp b/src/core/mixer.cpp index f3ce5b4..09927dc 100644 --- a/src/core/mixer.cpp +++ b/src/core/mixer.cpp @@ -80,7 +80,7 @@ float tick[TICKSIZE] = { /* lineInRec Records from line in. */ -void lineInRec(float *inBuf, unsigned frame) +void lineInRec(float* inBuf, unsigned frame) { if (!mh::hasArmedSampleChannels() || !kernelAudio::isInputEnabled() || !recording) return; @@ -106,7 +106,7 @@ void lineInRec(float *inBuf, unsigned frame) /* ProcessLineIn Computes line in peaks, plus handles "hear what you're playin'" thing. */ -void processLineIn(float *inBuf, unsigned frame) +void processLineIn(float* inBuf, unsigned frame) { if (!kernelAudio::isInputEnabled()) return; @@ -131,7 +131,7 @@ void processLineIn(float *inBuf, unsigned frame) /* clearAllBuffers Cleans up every buffer, both in Mixer and in channels. */ -void clearAllBuffers(float *outBuf, unsigned bufferSize) +void clearAllBuffers(float* outBuf, unsigned bufferSize) { memset(outBuf, 0, sizeof(float) * bufferSize); // out memset(vChanInToOut, 0, sizeof(float) * bufferSize); // inToOut vChan @@ -210,7 +210,7 @@ void sumChannels(unsigned frame) /* renderMetronome Generates metronome when needed and pastes it to the output buffer. */ -void renderMetronome(float *outBuf, unsigned frame) +void renderMetronome(float* outBuf, unsigned frame) { if (tockPlay) { outBuf[frame] += tock[tockTracker]; @@ -239,11 +239,13 @@ void renderMetronome(float *outBuf, unsigned frame) Final processing stage. Take each channel and process it (i.e. copy its content to the output buffer). Process plugins too, if any. */ -void renderIO(float *outBuf, float *inBuf) +void renderIO(float* outBuf, float* inBuf) { pthread_mutex_lock(&mutex_chans); - for (unsigned k=0; kprocess(outBuf, inBuf); + for (Channel* channel : channels) { + channel->process(outBuf, inBuf); + channel->preview(outBuf); + } pthread_mutex_unlock(&mutex_chans); #ifdef WITH_VST @@ -260,7 +262,7 @@ void renderIO(float *outBuf, float *inBuf) /* limitOutput Applies a very dumb hard limiter. */ -void limitOutput(float *outBuf, unsigned frame) +void limitOutput(float* outBuf, unsigned frame) { if (outBuf[frame] > 1.0f) outBuf[frame] = 1.0f; @@ -280,7 +282,7 @@ void limitOutput(float *outBuf, unsigned frame) /* computePeak */ -void computePeak(float *outBuf, unsigned frame) +void computePeak(float* outBuf, unsigned frame) { /* TODO it takes into account only left channel so far! */ if (outBuf[frame] > peakOut) @@ -294,7 +296,7 @@ void computePeak(float *outBuf, unsigned frame) Last touches after the output has been rendered: apply inToOut if any, apply output volume. */ -void finalizeOutput(float *outBuf, unsigned frame) +void finalizeOutput(float* outBuf, unsigned frame) { /* merge vChanInToOut, if enabled */ @@ -432,8 +434,8 @@ void allocVirtualInput(int frames) /* -------------------------------------------------------------------------- */ -int masterPlay(void *_outBuf, void *_inBuf, unsigned bufferSize, - double streamTime, RtAudioStreamStatus status, void *userData) +int masterPlay(void* _outBuf, void* _inBuf, unsigned bufferSize, + double streamTime, RtAudioStreamStatus status, void* userData) { if (!ready) return 0; @@ -442,8 +444,8 @@ int masterPlay(void *_outBuf, void *_inBuf, unsigned bufferSize, clock::recvJackSync(); #endif - float *outBuf = (float*) _outBuf; - float *inBuf = kernelAudio::isInputEnabled() ? (float*) _inBuf : nullptr; + float* outBuf = (float*) _outBuf; + float* inBuf = kernelAudio::isInputEnabled() ? (float*) _inBuf : nullptr; bufferSize *= 2; // stereo peakOut = 0.0f; // reset peak calculator peakIn = 0.0f; // reset peak calculator @@ -536,7 +538,7 @@ void mergeVirtualInput() continue; SampleChannel *ch = static_cast(channels.at(i)); if (ch->armed) - memcpy(ch->wave->data, vChanInput, clock::getTotalFrames() * sizeof(float)); + memcpy(ch->wave->getData(), vChanInput, clock::getTotalFrames() * sizeof(float)); } memset(vChanInput, 0, clock::getTotalFrames() * sizeof(float)); // clear vchan } diff --git a/src/core/mixerHandler.cpp b/src/core/mixerHandler.cpp index b099c1a..4de62d0 100644 --- a/src/core/mixerHandler.cpp +++ b/src/core/mixerHandler.cpp @@ -31,7 +31,6 @@ #include "../utils/log.h" #include "../glue/main.h" #include "../glue/channel.h" -#include "mixerHandler.h" #include "kernelMidi.h" #include "mixer.h" #include "const.h" @@ -49,6 +48,8 @@ #include "sampleChannel.h" #include "midiChannel.h" #include "wave.h" +#include "waveManager.h" +#include "mixerHandler.h" using std::vector; @@ -114,15 +115,15 @@ int getNewChanIndex() /* -------------------------------------------------------------------------- */ -bool uniqueSampleName(SampleChannel *ch, const string &name) +bool uniqueSampleName(SampleChannel* ch, const string& name) { for (unsigned i=0; itype != CHANNEL_SAMPLE) continue; - SampleChannel *other = (SampleChannel*) mixer::channels.at(i); - if (other->wave != nullptr && name == other->wave->name) + SampleChannel* other = (SampleChannel*) mixer::channels.at(i); + if (other->wave != nullptr && name == other->wave->getName()) return false; } return true; @@ -132,9 +133,9 @@ bool uniqueSampleName(SampleChannel *ch, const string &name) /* -------------------------------------------------------------------------- */ -Channel *addChannel(int type) +Channel* addChannel(int type) { - Channel *ch; + Channel* ch; int bufferSize = kernelAudio::getRealBufSize() * 2; if (type == CHANNEL_SAMPLE) @@ -142,6 +143,11 @@ Channel *addChannel(int type) else ch = new MidiChannel(bufferSize); + if (!ch->allocBuffers()) { + delete ch; + return nullptr; + } + while (true) { if (pthread_mutex_trylock(&mixer::mutex_chans) != 0) continue; @@ -160,7 +166,7 @@ Channel *addChannel(int type) /* -------------------------------------------------------------------------- */ -int deleteChannel(Channel *ch) +int deleteChannel(Channel* ch) { int index = -1; for (unsigned i=0; itype != CHANNEL_SAMPLE) continue; SampleChannel *ch = static_cast(mixer::channels.at(i)); - if (ch->wave && ch->wave->isLogical) + if (ch->wave && ch->wave->isLogical()) return true; } return false; @@ -224,7 +230,7 @@ bool hasEditedSamples() if (mixer::channels.at(i)->type != CHANNEL_SAMPLE) continue; SampleChannel *ch = static_cast(mixer::channels.at(i)); - if (ch->wave && ch->wave->isEdited) + if (ch->wave && ch->wave->isEdited()) return true; } return false; @@ -306,27 +312,31 @@ bool startInputRec() { int channelsReady = 0; - for (unsigned i=0; icanInputRec()) + if (!channel->canInputRec()) continue; - SampleChannel *ch = (SampleChannel*) mixer::channels.at(i); + SampleChannel* ch = static_cast(channel); /* Allocate empty sample for the current channel. */ - if (!ch->allocEmpty(clock::getTotalFrames(), conf::samplerate, patch::lastTakeId)) - { + Wave* wave = nullptr; + int result = waveManager::createEmpty(clock::getTotalFrames(), + conf::samplerate, string("TAKE-" + gu_toString(patch::lastTakeId)), &wave); + if (result != G_RES_OK) { gu_log("[startInputRec] unable to allocate new Wave in chan %d!\n", ch->index); continue; } - /* Increase lastTakeId until the sample name TAKE-[n] is unique */ + ch->pushWave(wave, false); // false: don't generate name, we provide it + + /* Increase lastTakeId until the sample name TAKE-[n] is unique. */ - while (!uniqueSampleName(ch, ch->wave->name)) { + while (!uniqueSampleName(ch, ch->wave->getName())) { patch::lastTakeId++; - ch->wave->name = "TAKE-" + gu_itoa(patch::lastTakeId); + ch->wave->setName("TAKE-" + gu_toString(patch::lastTakeId)); } gu_log("[startInputRec] start input recs using chan %d with size %d " diff --git a/src/core/patch.cpp b/src/core/patch.cpp index f9152e0..e0daf2e 100644 --- a/src/core/patch.cpp +++ b/src/core/patch.cpp @@ -198,7 +198,7 @@ bool readChannels(json_t *jContainer) json_t *jChannel; json_array_foreach(jChannels, channelIndex, jChannel) { - string channelIndexStr = "channel " + gu_itoa(channelIndex); + string channelIndexStr = "channel " + gu_toString(channelIndex); if (!storager::checkObject(jChannel, channelIndexStr.c_str())) return 0; @@ -262,7 +262,7 @@ bool readColumns(json_t *jContainer) json_t *jColumn; json_array_foreach(jColumns, columnIndex, jColumn) { - string columnIndexStr = "column " + gu_itoa(columnIndex); + string columnIndexStr = "column " + gu_toString(columnIndex); if (!storager::checkObject(jColumn, columnIndexStr.c_str())) return 0; diff --git a/src/core/plugin.cpp b/src/core/plugin.cpp index 3616392..0402818 100644 --- a/src/core/plugin.cpp +++ b/src/core/plugin.cpp @@ -56,15 +56,9 @@ Plugin::Plugin(juce::AudioPluginInstance *plugin, double samplerate, /* Try to enable editor (i.e. plugin's UI) */ - if (plugin->getActiveEditor() != nullptr) { - gu_log("[Plugin] plugin has an already active editor!\n"); - return; - } ui = plugin->createEditorIfNeeded(); - if (ui == nullptr) { + if (ui == nullptr) gu_log("[Plugin] unable to create editor, the plugin might be GUI-less!\n"); - return; - } plugin->prepareToPlay(samplerate, buffersize); diff --git a/src/core/sampleChannel.cpp b/src/core/sampleChannel.cpp index fd83e52..3121015 100644 --- a/src/core/sampleChannel.cpp +++ b/src/core/sampleChannel.cpp @@ -27,10 +27,10 @@ #include #include +#include #include "../utils/log.h" #include "../utils/fs.h" #include "../utils/string.h" -#include "sampleChannel.h" #include "patch.h" #include "const.h" #include "conf.h" @@ -39,9 +39,11 @@ #include "wave.h" #include "pluginHost.h" #include "waveFx.h" +#include "waveManager.h" #include "mixerHandler.h" #include "kernelMidi.h" #include "kernelAudio.h" +#include "sampleChannel.h" using std::string; @@ -50,13 +52,17 @@ using namespace giada::m; SampleChannel::SampleChannel(int bufferSize, bool inputMonitor) : Channel (CHANNEL_SAMPLE, STATUS_EMPTY, bufferSize), + rsmp_state (nullptr), + pChan (nullptr), + vChanPreview (nullptr), frameRewind (-1), + begin (0), + end (0), boost (G_DEFAULT_BOOST), pitch (G_DEFAULT_PITCH), + trackerPreview (0), wave (nullptr), tracker (0), - begin (0), - end (0), mode (G_DEFAULT_CHANMODE), qWait (false), fadeinOn (false), @@ -65,12 +71,10 @@ SampleChannel::SampleChannel(int bufferSize, bool inputMonitor) fadeoutVol (1.0f), fadeoutTracker (0), fadeoutStep (G_DEFAULT_FADEOUT_STEP), - inputMonitor (inputMonitor), - midiInReadActions(0x0), - midiInPitch (0x0) + inputMonitor (inputMonitor), + midiInReadActions(0x0), + midiInPitch (0x0) { - rsmp_state = src_new(SRC_LINEAR, 2, nullptr); - pChan = (float *) malloc(kernelAudio::getRealBufSize() * 2 * sizeof(float)); } @@ -79,20 +83,54 @@ SampleChannel::SampleChannel(int bufferSize, bool inputMonitor) SampleChannel::~SampleChannel() { - if (wave) + if (wave != nullptr) delete wave; - src_delete(rsmp_state); - free(pChan); + if (rsmp_state != nullptr) + src_delete(rsmp_state); + if (pChan != nullptr) + delete[] pChan; + if (vChanPreview != nullptr) + delete[] vChanPreview; } /* -------------------------------------------------------------------------- */ -void SampleChannel::copy(const Channel *_src, pthread_mutex_t *pluginMutex) +bool SampleChannel::allocBuffers() +{ + if (!Channel::allocBuffers()) + return false; + + rsmp_state = src_new(SRC_LINEAR, 2, nullptr); + if (rsmp_state == nullptr) { + gu_log("[SampleChannel::allocBuffers] unable to alloc memory for SRC_STATE!\n"); + return false; + } + + pChan = new (std::nothrow) float[bufferSize]; + if (pChan == nullptr) { + gu_log("[SampleChannel::allocBuffers] unable to alloc memory for pChan!\n"); + return false; + } + + vChanPreview = new (std::nothrow) float[bufferSize]; + if (vChanPreview == nullptr) { + gu_log("[SampleChannel::allocBuffers] unable to alloc memory for vChanPreview!\n"); + return false; + } + + return true; +} + + +/* -------------------------------------------------------------------------- */ + + +void SampleChannel::copy(const Channel* _src, pthread_mutex_t* pluginMutex) { Channel::copy(_src, pluginMutex); - SampleChannel *src = (SampleChannel *) _src; + const SampleChannel* src = static_cast(_src); tracker = src->tracker; begin = src->begin; end = src->end; @@ -109,11 +147,8 @@ void SampleChannel::copy(const Channel *_src, pthread_mutex_t *pluginMutex) fadeoutEnd = src->fadeoutEnd; setPitch(src->pitch); - if (src->wave) { - Wave *w = new Wave(*src->wave); // invoke Wave's copy constructor - pushWave(w); - generateUniqueSampleName(); - } + if (src->wave) + pushWave(new Wave(*src->wave)); // invoke Wave's copy constructor } @@ -122,10 +157,10 @@ void SampleChannel::copy(const Channel *_src, pthread_mutex_t *pluginMutex) void SampleChannel::generateUniqueSampleName() { - string oldName = wave->name; - int k = 1; // Start from k = 1, zero is too nerdy - while (!mh::uniqueSampleName(this, wave->name)) { - wave->updateName((oldName + "-" + gu_itoa(k)).c_str()); + string oldName = wave->getName(); + int k = 0; + while (!mh::uniqueSampleName(this, wave->getName())) { + wave->setName(oldName + "-" + gu_toString(k)); k++; } } @@ -139,8 +174,8 @@ void SampleChannel::clear() /** TODO - these memsets can be done only if status PLAY (if below), * but it would require extra clearPChan calls when samples stop */ - std::memset(vChan, 0, sizeof(float) * bufferSize); - std::memset(pChan, 0, sizeof(float) * bufferSize); + std::memset(vChan, 0, sizeof(float) * bufferSize); + std::memset(pChan, 0, sizeof(float) * bufferSize); if (status & (STATUS_PLAY | STATUS_ENDING)) { tracker = fillChan(vChan, tracker, 0); @@ -226,46 +261,96 @@ void SampleChannel::onBar(int frame) /* -------------------------------------------------------------------------- */ -int SampleChannel::save(const char *path) +int SampleChannel::save(const char* path) { - return wave->writeData(path); + return waveManager::save(wave, path); } /* -------------------------------------------------------------------------- */ -void SampleChannel::setBegin(int v) +void SampleChannel::setBegin(int f) { - if (v < 0) + /* TODO - Opaque channel's count - everything in SampleChannel should be + frame-based, not sample-based. I.e. Remove * wave->getChannels() stuff. */ + + if (f < 0) begin = 0; else - if (v > wave->size) - begin = wave->size - 2; + if (f > wave->getSize()) + begin = wave->getSize() * wave->getChannels(); else - if (v >= end) - begin = end - 2; + if (f >= end) + begin = end - wave->getChannels(); else - begin = v; + begin = f * wave->getChannels(); + tracker = begin; + trackerPreview = begin; } /* -------------------------------------------------------------------------- */ -void SampleChannel::setEnd(int v) +void SampleChannel::setEnd(int f) { - if (v < 0) - end = begin + 2; + /* TODO - Opaque channel's count - everything in SampleChannel should be + frame-based, not sample-based. I.e. Remove * wave->getChannels() stuff. */ + + if (f < 0) + end = begin + wave->getChannels(); else - if (v > wave->size) - end = wave->size; + if (f >= wave->getSize()) + end = (wave->getSize() - 1) * wave->getChannels(); else - if (v <= begin) - end = begin + 2; + if (f <= begin) + end = begin + wave->getChannels(); else - end = v; + end = f * wave->getChannels(); +} + + +/* -------------------------------------------------------------------------- */ + + +int SampleChannel::getBegin() +{ + /* TODO - Opaque channel's count - everything in SampleChannel should be + frame-based, not sample-based. I.e. Remove / wave->getChannels() stuff. */ + + return begin / wave->getChannels(); +} + + +int SampleChannel::getEnd() +{ + /* TODO - Opaque channel's count - everything in SampleChannel should be + frame-based, not sample-based. I.e. Remove / wave->getChannels() stuff. */ + + return end / wave->getChannels(); +} + + +/* -------------------------------------------------------------------------- */ + + +void SampleChannel::setTrackerPreview(int f) +{ + /* TODO - Opaque channel's count - everything in SampleChannel should be + frame-based, not sample-based. I.e. Remove * wave->getChannels() stuff. */ + + trackerPreview = f * wave->getChannels(); +} + + +int SampleChannel::getTrackerPreview() const +{ + /* TODO - Opaque channel's count - everything in SampleChannel should be + frame-based, not sample-based. I.e. Remove / wave->getChannels() stuff. */ + + return trackerPreview / wave->getChannels(); } @@ -314,7 +399,7 @@ void SampleChannel::rewind() /* -------------------------------------------------------------------------- */ -void SampleChannel::parseAction(recorder::action *a, int localFrame, +void SampleChannel::parseAction(recorder::action* a, int localFrame, int globalFrame, int quantize, bool mixerIsRunning) { if (readActions == false) @@ -548,8 +633,11 @@ void SampleChannel::quantize(int index, int localFrame) int SampleChannel::getPosition() { + /* TODO - Opaque channel's count - everything in SampleChannel should be + frame-based, not sample-based. I.e. Remove / wave->getChannels() stuff. */ + if (status & ~(STATUS_EMPTY | STATUS_MISSING | STATUS_OFF)) // if is not (...) - return tracker - begin; + return (tracker - begin) / wave->getChannels(); else return -1; } @@ -673,6 +761,15 @@ void SampleChannel::setReadActions(bool v, bool recsStopOnChanHalt) /* -------------------------------------------------------------------------- */ +void SampleChannel::setOnEndPreviewCb(std::function f) +{ + onPreviewEnd = f; +} + + +/* -------------------------------------------------------------------------- */ + + void SampleChannel::setFadeIn(bool internal) { if (internal) mute_i = false; // remove mute before fading in @@ -713,14 +810,13 @@ void SampleChannel::setXFade(int frame) /* -------------------------------------------------------------------------- */ +/* On reset, if frame > 0 and in play, fill again pChan to create something like +this: -/* on reset, if frame > 0 and in play, fill again pChan to create - * something like this: - * - * |abcdefabcdefab*abcdefabcde| - * [old data-----]*[new data--] - * - * */ + |abcdefabcdefab*abcdefabcde| + [old data-----]*[new data--] + +*/ void SampleChannel::reset(int frame) { @@ -738,7 +834,7 @@ void SampleChannel::reset(int frame) void SampleChannel::empty() { status = STATUS_OFF; - if (wave) { + if (wave != nullptr) { delete wave; wave = nullptr; } @@ -755,42 +851,22 @@ void SampleChannel::empty() /* -------------------------------------------------------------------------- */ -void SampleChannel::pushWave(Wave *w) +void SampleChannel::pushWave(Wave* w, bool generateName) { + sendMidiLplay(); // FIXME - why here?!?! wave = w; status = STATUS_OFF; - sendMidiLplay(); // FIXME - why here?!?! begin = 0; - end = wave->size; -} - - -/* -------------------------------------------------------------------------- */ - - -bool SampleChannel::allocEmpty(int frames, int samplerate, int takeId) -{ - Wave *w = new Wave(); - if (!w->allocEmpty(frames, samplerate)) - return false; - - w->name = "TAKE-" + gu_itoa(takeId); - w->pathfile = gu_getCurrentPath() + G_SLASH + w->name; - wave = w; - status = STATUS_OFF; - begin = 0; - end = wave->size; - - sendMidiLplay(); // FIXME - why here?!?! - - return true; + end = (wave->getSize() - 1) * wave->getChannels(); // TODO - Opaque channels' count + if (generateName) + generateUniqueSampleName(); } /* -------------------------------------------------------------------------- */ -void SampleChannel::process(float *outBuffer, float *inBuffer) +void SampleChannel::process(float* outBuffer, float* inBuffer) { /* If armed and inbuffer is not nullptr (i.e. input device available) and input monitor is on, copy input buffer to vChan: this enables the input @@ -815,6 +891,42 @@ void SampleChannel::process(float *outBuffer, float *inBuffer) /* -------------------------------------------------------------------------- */ +void SampleChannel::preview(float* outBuffer) +{ + if (previewMode == G_PREVIEW_NONE) + return; + + std::memset(vChanPreview, 0, sizeof(float) * bufferSize); + + /* If the tracker exceedes the end point and preview is looped, split the + rendering as in SampleChannel::reset(). */ + + if (trackerPreview + bufferSize >= end) { + int offset = end - trackerPreview; + trackerPreview = fillChan(vChanPreview, trackerPreview, 0, false); + trackerPreview = begin; + if (previewMode == G_PREVIEW_LOOP) + trackerPreview = fillChan(vChanPreview, begin, offset, false); + else + if (previewMode == G_PREVIEW_NORMAL) { + previewMode = G_PREVIEW_NONE; + if (onPreviewEnd) + onPreviewEnd(); + } + } + else + trackerPreview = fillChan(vChanPreview, trackerPreview, 0, false); + + for (int j=0; j FILENAME_MAX) - return SAMPLE_PATH_TOO_LONG; - - Wave *w = new Wave(); - - if (!w->open(file)) { - gu_log("[SampleChannel] %s: read error\n", file); - delete w; - return SAMPLE_READ_ERROR; - } - - if (w->channels() > 2) { - gu_log("[SampleChannel] %s: unsupported multichannel wave\n", file); - delete w; - return SAMPLE_MULTICHANNEL; - } - - if (!w->readData()) { - delete w; - return SAMPLE_READ_ERROR; - } - - if (w->channels() == 1) /** FIXME: error checking */ - wfx_monoToStereo(w); - - if (w->rate() != samplerate) { - gu_log("[SampleChannel] input rate (%d) != system rate (%d), conversion needed\n", - w->rate(), samplerate); - w->resample(rsmpQuality, samplerate); - } - - pushWave(w); - generateUniqueSampleName(); - - gu_log("[SampleChannel] %s loaded in channel %d\n", file, index); - return SAMPLE_LOADED_OK; -} - - -/* -------------------------------------------------------------------------- */ - - int SampleChannel::readPatch(const string &basePath, int i, pthread_mutex_t *pluginMutex, int samplerate, int rsmpQuality) { @@ -944,17 +1007,20 @@ int SampleChannel::readPatch(const string &basePath, int i, midiInPitch = pch->midiInPitch; inputMonitor = pch->inputMonitor; - int res = load((basePath + pch->samplePath).c_str(), samplerate, rsmpQuality); - if (res == SAMPLE_LOADED_OK) { + Wave *w = nullptr; + int res = waveManager::create(basePath + pch->samplePath, &w); + + if (res == G_RES_OK) { + pushWave(w); setBegin(pch->begin); setEnd (pch->end); setPitch(pch->pitch); } else { - if (res == SAMPLE_LEFT_EMPTY) + if (res == G_RES_ERR_NO_DATA) status = STATUS_EMPTY; else - if (res == SAMPLE_READ_ERROR) + if (res == G_RES_ERR_IO) status = STATUS_MISSING; sendMidiLplay(); // FIXME - why sending MIDI lightning if sample status is wrong? } @@ -1058,9 +1124,9 @@ int SampleChannel::writePatch(int i, bool isProject) patch::channel_t *pch = &patch::channels.at(pchIndex); if (wave != nullptr) { - pch->samplePath = wave->pathfile; + pch->samplePath = wave->getPath(); if (isProject) - pch->samplePath = gu_basename(wave->pathfile); // make it portable + pch->samplePath = gu_basename(wave->getPath()); // make it portable } else pch->samplePath = ""; @@ -1101,7 +1167,7 @@ int SampleChannel::fillChan(float *dest, int start, int offset, bool rewind) * end) */ if (start+bufferSize-offset <= end) { - memcpy(dest+offset, wave->data+start, (bufferSize-offset)*sizeof(float)); + memcpy(dest+offset, wave->getData()+start, (bufferSize-offset)*sizeof(float)); position = start+bufferSize-offset; if (rewind) frameRewind = -1; @@ -1111,7 +1177,7 @@ int SampleChannel::fillChan(float *dest, int start, int offset, bool rewind) * is smaller than 'dest' */ else { - memcpy(dest+offset, wave->data+start, (end-start)*sizeof(float)); + memcpy(dest+offset, wave->getData()+start, (end-start)*sizeof(float)); position = end; if (rewind) frameRewind = end-start+offset; @@ -1119,7 +1185,7 @@ int SampleChannel::fillChan(float *dest, int start, int offset, bool rewind) } else { - rsmp_data.data_in = wave->data+start; // source data + rsmp_data.data_in = wave->getData()+start; // source data rsmp_data.input_frames = (end-start)/2; // how many readable bytes rsmp_data.data_out = dest+offset; // destination (processed data) rsmp_data.output_frames = (bufferSize-offset)/2; // how many bytes to process diff --git a/src/core/sampleChannel.h b/src/core/sampleChannel.h index f8203ef..8435dfe 100644 --- a/src/core/sampleChannel.h +++ b/src/core/sampleChannel.h @@ -29,6 +29,7 @@ #define G_SAMPLE_CHANNEL_H +#include #include #include "channel.h" @@ -44,33 +45,46 @@ private: /* rsmp_state, rsmp_data * structs from libsamplerate */ - SRC_STATE *rsmp_state; + SRC_STATE* rsmp_state; SRC_DATA rsmp_data; /* pChan Extra virtual channel for processing resampled data. */ - float *pChan; + float* pChan; + + /* pChan + Extra virtual channel for audio preview. */ + + float* vChanPreview; /* frameRewind Exact frame in which a rewind occurs. */ int frameRewind; + int begin; + int end; float boost; float pitch; + int trackerPreview; // chan position for audio preview + + /* onPreviewEnd + A callback fired when audio preview ends. */ + + std::function onPreviewEnd; /* fillChan - * copy from wave to *dest and resample data from wave, if necessary. - * Start to fill pChan from byte 'offset'. If rewind=false don't - * rewind internal tracker. Returns new sample position, in frames */ + Fills 'dest' buffer at point 'offset' with wave data taken from 'start'. If + rewind=false don't rewind internal tracker. Returns new sample position, + in frames. It resamples data if pitch != 1.0f. */ - int fillChan(float *dest, int start, int offset, bool rewind=true); + int fillChan(float* dest, int start, int offset, bool rewind=true); /* clearChan * set data to zero from start to bufferSize-1. */ - void clearChan(float *dest, int start); + void clearChan(float* dest, int start); /* calcFadeoutStep * how many frames are left before the end of the sample? Is there @@ -94,9 +108,10 @@ public: SampleChannel(int bufferSize, bool inputMonitor); ~SampleChannel(); - void copy(const Channel *src, pthread_mutex_t *pluginMutex) override; + void copy(const Channel* src, pthread_mutex_t* pluginMutex) override; void clear() override; - void process(float *outBuffer, float *inBuffer) override; + void process(float* outBuffer, float* inBuffer) override; + void preview(float* outBuffer) override; void start(int frame, bool doQuantize, int quantize, bool mixerIsRunning, bool forceStart, bool isUserGenerated) override; void kill(int frame) override; @@ -106,17 +121,16 @@ public: void rewind() override; void setMute(bool internal) override; void unsetMute(bool internal) override; - int readPatch(const std::string &basePath, int i, pthread_mutex_t *pluginMutex, + int readPatch(const std::string& basePath, int i, pthread_mutex_t* pluginMutex, int samplerate, int rsmpQuality) override; int writePatch(int i, bool isProject) override; void quantize(int index, int localFrame) override; void onZero(int frame, bool recsStopOnChanHalt) override; void onBar(int frame) override; - void parseAction(giada::m::recorder::action *a, int localFrame, int globalFrame, + void parseAction(giada::m::recorder::action* a, int localFrame, int globalFrame, int quantize, bool mixerIsRunning) override; bool canInputRec() override; - - int load(const char *file, int samplerate, int rsmpQuality); + bool allocBuffers() override; void reset(int frame); @@ -124,14 +138,15 @@ public: * prepare channel for fade, mixer will take care of the process * during master play. */ - void setFadeIn (bool internal); - void setFadeOut (int actionPostFadeout); - void setXFade (int frame); + void setFadeIn(bool internal); + void setFadeOut(int actionPostFadeout); + void setXFade(int frame); /* pushWave - * add a new wave to an existing channel. */ + Adds a new wave to an existing channel. It also generates a unique name + on request. */ - void pushWave(Wave *w); + void pushWave(Wave* w, bool generateName=true); /* getPosition * returns the position of an active sample. If EMPTY o MISSING @@ -145,33 +160,26 @@ public: void sum(int frame, bool running); - /* setPitch - * updates the pitch value and chanStart+chanEnd accordingly. */ void setPitch(float v); float getPitch(); - - /* setStart/end - * change begin/end read points in sample. */ - - void setBegin(int v); - void setEnd (int v); + void setBegin(int f); + int getBegin(); + void setEnd(int f); + int getEnd(); + void setTrackerPreview(int f); + int getTrackerPreview() const; /* save * save sample to file. */ - int save(const char *path); + int save(const char* path); /* hardStop * stop the channel immediately, no further checks. */ void hardStop(int frame); - /* allocEmpty - * alloc an empty wave used in input recordings. */ - - bool allocEmpty(int frames, int samplerate, int takeId); - /* setReadActions * if enabled (v == true), recorder will read actions from this channel. If * recsStopOnChanHalt == true, stop reading actions right away. */ @@ -181,23 +189,23 @@ public: void setBoost(float v); float getBoost(); + void setOnEndPreviewCb(std::function f); + /* ------------------------------------------------------------------------ */ - Wave *wave; - int tracker; // chan position - int begin; - int end; - int mode; // mode: see const.h - bool qWait; // quantizer wait - bool fadeinOn; - float fadeinVol; - bool fadeoutOn; - float fadeoutVol; // fadeout volume - int fadeoutTracker; // tracker fadeout, xfade only - float fadeoutStep; // fadeout decrease - int fadeoutType; // xfade or fadeout - int fadeoutEnd; // what to do when fadeout ends - bool inputMonitor; + Wave* wave; + int tracker; // chan position + int mode; // mode: see const.h + bool qWait; // quantizer wait + bool fadeinOn; + float fadeinVol; + bool fadeoutOn; + float fadeoutVol; // fadeout volume + int fadeoutTracker; // tracker fadeout, xfade only + float fadeoutStep; // fadeout decrease + int fadeoutType; // xfade or fadeout + int fadeoutEnd; // what to do when fadeout ends + bool inputMonitor; /* midi stuff */ diff --git a/src/core/wave.cpp b/src/core/wave.cpp index 1f821b6..28edd18 100644 --- a/src/core/wave.cpp +++ b/src/core/wave.cpp @@ -27,14 +27,10 @@ * -------------------------------------------------------------------------- */ -#include -#include +#include #include // memcpy -#include -#include #include "../utils/fs.h" #include "../utils/log.h" -#include "init.h" #include "const.h" #include "wave.h" @@ -43,244 +39,156 @@ using std::string; Wave::Wave() -: data (nullptr), - size (0), - isLogical(0), - isEdited (0) {} +: m_data (nullptr), + m_size (0), + m_logical(0), + m_edited (0) {} /* -------------------------------------------------------------------------- */ -Wave::~Wave() +Wave::Wave(float* data, int size, int channels, int rate, int bits, + const std::string& path) +: m_data (data), + m_size (size), + m_channels(channels), + m_rate (rate), + m_bits (bits), + m_logical (false), + m_edited (false), + m_path (path), + m_name (gu_stripExt(gu_basename(path))) { - clear(); -} +} /* -------------------------------------------------------------------------- */ -Wave::Wave(const Wave &other) -: data (nullptr), - size (0), - isLogical(false), - isEdited (false) +Wave::~Wave() { - size = other.size; - data = new float[size]; - memcpy(data, other.data, size * sizeof(float)); - memcpy(&inHeader, &other.inHeader, sizeof(other.inHeader)); - pathfile = other.pathfile; - name = other.name; - isLogical = true; + clear(); } + /* -------------------------------------------------------------------------- */ -int Wave::open(const char *f) +Wave::Wave(const Wave& other) +: m_data (nullptr), + m_size (other.m_size), + m_channels(other.m_channels), + m_rate (other.m_rate), + m_bits (other.m_bits), + m_logical (true), // a cloned wave does not exist on disk + m_edited (false), + m_path (other.m_path), + m_name (other.m_name) { - pathfile = f; - name = gu_stripExt(gu_basename(f)); - fileIn = sf_open(f, SFM_READ, &inHeader); - - if (fileIn == nullptr) { - gu_log("[wave] unable to read %s. %s\n", f, sf_strerror(fileIn)); - pathfile = ""; - name = ""; - return 0; - } - - isLogical = false; - isEdited = false; - - return 1; + m_data = new float[m_size]; + memcpy(m_data, other.m_data, m_size * sizeof(float)); } /* -------------------------------------------------------------------------- */ -/* how to read and write with libsndfile: - * - * a frame consists of all items (samples) that belong to the same - * point in time. So in each frame there are as many items as there - * are channels. - * - * Quindi: - * frame = [item, item, ...] - * In pratica: - * frame1 = [itemLeft, itemRight] - * frame2 = [itemLeft, itemRight] - * ... - */ - -int Wave::readData() + +void Wave::clear() { - size = inHeader.frames * inHeader.channels; - data = (float *) malloc(size * sizeof(float)); - if (data == nullptr) { - gu_log("[wave] unable to allocate memory\n"); - return 0; - } - - if (sf_read_float(fileIn, data, size) != size) - gu_log("[wave] warning: incomplete read!\n"); - - sf_close(fileIn); - return 1; + free(); + m_path = ""; + m_size = 0; } /* -------------------------------------------------------------------------- */ -int Wave::writeData(const char *f) +void Wave::free() { - /* prepare the header for output file */ - - outHeader.samplerate = inHeader.samplerate; - outHeader.channels = inHeader.channels; - outHeader.format = inHeader.format; - - fileOut = sf_open(f, SFM_WRITE, &outHeader); - if (fileOut == nullptr) { - gu_log("[wave] unable to open %s for exporting\n", f); - return 0; - } - - int out = sf_write_float(fileOut, data, size); - if (out != (int) size) { - gu_log("[wave] error while exporting %s! %s\n", f, sf_strerror(fileOut)); - return 0; - } - - isLogical = false; - isEdited = false; - sf_close(fileOut); - return 1; + if (m_data == nullptr) + return; + delete[] m_data; + m_data = nullptr; } /* -------------------------------------------------------------------------- */ -void Wave::clear() +string Wave::getBasename(bool ext) const { - if (data != nullptr) { - free(data); - data = nullptr; - pathfile = ""; - size = 0; - } + return ext ? gu_basename(m_path) : gu_stripExt(gu_basename(m_path)); } /* -------------------------------------------------------------------------- */ -int Wave::allocEmpty(unsigned __size, unsigned samplerate) +void Wave::setName(const string& n) { - /* the caller must pass a __size for stereo values */ - - /// FIXME - this way if malloc fails size becomes wrong - size = __size; - data = (float *) malloc(size * sizeof(float)); - if (data == nullptr) { - gu_log("[wave] unable to allocate memory\n"); - return 0; - } + string ext = gu_getExt(m_path); + m_name = gu_stripExt(gu_basename(n)); + m_path = gu_dirname(m_path) + G_SLASH + m_name + "." + ext; + m_logical = true; - memset(data, 0, sizeof(float) * size); /// FIXME - is it useful? - - inHeader.samplerate = samplerate; - inHeader.channels = 2; - inHeader.format = SF_FORMAT_WAV | SF_FORMAT_FLOAT; // wave only - - isLogical = true; - return 1; + /* A wave with updated m_name must become logical, since the underlying file + does not exist yet. */ } /* -------------------------------------------------------------------------- */ -int Wave::resample(int quality, int newRate) -{ - float ratio = newRate / (float) inHeader.samplerate; - int newSize = ceil(size * ratio); - if (newSize % 2 != 0) // libsndfile goes crazy with odd size in case of saving - newSize++; - - float *tmp = (float *) malloc(newSize * sizeof(float)); - if (!tmp) { - gu_log("[wave] unable to allocate memory for resampling\n"); - return -1; - } - - SRC_DATA src_data; - src_data.data_in = data; - src_data.input_frames = size/2; // in frames, i.e. /2 (stereo) - src_data.data_out = tmp; - src_data.output_frames = newSize/2; // in frames, i.e. /2 (stereo) - src_data.src_ratio = ratio; - - gu_log("[wave] resampling: new size=%d (%d frames)\n", newSize, newSize/2); - - int ret = src_simple(&src_data, quality, 2); - if (ret != 0) { - gu_log("[wave] resampling error: %s\n", src_strerror(ret)); - return 0; - } - - free(data); - data = tmp; - size = newSize; - inHeader.samplerate = newRate; - return 1; -} +int Wave::getRate() const { return m_rate; } +int Wave::getChannels() const { return m_channels; } +std::string Wave::getPath() const { return m_path; } +std::string Wave::getName() const { return m_name; } +float* Wave::getData() const { return m_data; } +int Wave::getSize() const { return m_size / m_channels; } +int Wave::getBits() const { return m_bits; } +bool Wave::isLogical() const { return m_logical; } +bool Wave::isEdited() const { return m_edited; } /* -------------------------------------------------------------------------- */ -string Wave::basename(bool ext) const +int Wave::getDuration() const { - return ext ? gu_basename(pathfile) : gu_stripExt(gu_basename(pathfile)); -} - -string Wave::extension() const -{ - return gu_getExt(pathfile); + return m_size / m_channels / m_rate; } /* -------------------------------------------------------------------------- */ -void Wave::updateName(const char *n) +float* Wave::getFrame(int f) const { - string ext = gu_getExt(pathfile); - name = gu_stripExt(gu_basename(n)); - pathfile = gu_dirname(pathfile) + G_SLASH + name + "." + ext; - isLogical = true; + assert(f >= 0); + assert(f < getSize()); - /* a wave with updated name must become logical, since the underlying - * file does not exist yet. */ + f *= m_channels; // convert frame to sample + return m_data + f; // i.e. a pointer to m_data[f] } /* -------------------------------------------------------------------------- */ -int Wave::rate () { return inHeader.samplerate; } -int Wave::channels() { return inHeader.channels; } -int Wave::frames () { return inHeader.frames; } +void Wave::setRate(int v) { m_rate = v; } +void Wave::setChannels(int v) { m_channels = v; } +void Wave::setPath(const string& p) { m_path = p; } +void Wave::setLogical(bool l) { m_logical = l; } +void Wave::setEdited(bool e) { m_edited = e; } /* -------------------------------------------------------------------------- */ -void Wave::rate (int v) { inHeader.samplerate = v; } -void Wave::channels(int v) { inHeader.channels = v; } -void Wave::frames (int v) { inHeader.frames = v; } +void Wave::setData(float* d, int size) +{ + m_data = d; + m_size = size; +} diff --git a/src/core/wave.h b/src/core/wave.h index 11c721d..7c9f56b 100644 --- a/src/core/wave.h +++ b/src/core/wave.h @@ -33,56 +33,72 @@ #include #include +#include "const.h" class Wave { private: - SNDFILE *fileIn; - SNDFILE *fileOut; - SF_INFO inHeader; - SF_INFO outHeader; + float* m_data; + int m_size; // Wave size in bytes (size in stereo: size / 2) + int m_channels; + int m_rate; + int m_bits; + bool m_logical; // memory only (a take) + bool m_edited; // edited via editor + + std::string m_path; // E.g. /path/to/my/sample.wav + std::string m_name; // Sample name (can be changed) public: Wave(); + Wave(float* data, int size, int channels, int rate, int bits, const std::string& path); ~Wave(); - Wave(const Wave &other); + Wave(const Wave& other); - std::string pathfile; // full path + sample name - std::string name; // sample name (changeable) + void setRate(int v); + void setChannels(int v); + void setPath(const std::string& p); + void setName(const std::string& p); + void setData(float* data, int size); + void setLogical(bool l); + void setEdited(bool e); - float *data; - int size; // wave size (size in stereo: size / 2) - bool isLogical; // memory only (a take) - bool isEdited; // edited via editor + std::string getBasename(bool ext=false) const; + int getRate() const; + int getChannels() const; + std::string getPath() const; + std::string getName() const; + int getBits() const; + float* getData() const; + int getSize() const; // with channels count + int getDuration() const; + bool isLogical() const; + bool isEdited() const; - int rate (); - int channels(); - int frames (); - void rate (int v); - void channels(int v); - void frames (int v); + /* clear + Resets Wave to init state. */ - std::string basename(bool ext=false) const; - std::string extension() const; + void clear(); - void updateName(const char *n); - int open (const char *f); - int readData (); - int writeData (const char *f); - void clear (); + /* free + Frees memory, leaving everything else untouched. */ - /* allocEmpty - * alloc an empty waveform. */ + void free(); - int allocEmpty(unsigned size, unsigned samplerate); + /* getFrame + Given a frame 'f', returns a pointer to it. This is useful for digging inside + a frame, i.e. parsing each channel. How to use it: - /* resample - * simple algorithm for one-shot resampling. */ + float* frame = w->getFrame(40); + for (int i=0; igetChannels(); i++) + ... frame[i] ... + */ + + float* getFrame(int f) const; - int resample(int quality, int newRate); }; #endif diff --git a/src/core/waveFx.cpp b/src/core/waveFx.cpp index d31bead..2f0e8ea 100644 --- a/src/core/waveFx.cpp +++ b/src/core/waveFx.cpp @@ -2,8 +2,6 @@ * * Giada - Your Hardcore Loopmachine * - * waveFx - * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2017 Giovanni A. Zuliani | Monocasual @@ -33,12 +31,34 @@ #include "waveFx.h" -float wfx_normalizeSoft(Wave *w) +namespace giada { +namespace m { +namespace wfx +{ +namespace +{ +void fadeFrame(Wave* w, int i, float val) +{ + float* frame = w->getFrame(i); + for (int j=0; jgetChannels(); j++) + frame[j] *= val; +} +}; // {anonymous} + + +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ + + +float normalizeSoft(Wave *w) { float peak = 0.0f; float abs = 0.0f; - for (int i=0; isize; i++) { // i++: both L and R samples - abs = fabs(w->data[i]); + for (int i=0; igetSize(); i++) { + float* frame = w->getFrame(i); + for (int j=0; jgetChannels(); j++) // Find highest value in any channel + abs = fabs(frame[j]); if (abs > peak) peak = abs; } @@ -56,48 +76,47 @@ float wfx_normalizeSoft(Wave *w) /* -------------------------------------------------------------------------- */ -bool wfx_monoToStereo(Wave *w) +int monoToStereo(Wave* w) { - unsigned newSize = w->size * 2; - float *dataNew = (float *) malloc(newSize * sizeof(float)); - if (dataNew == nullptr) { - gu_log("[wfx] unable to allocate memory for mono>stereo conversion\n"); - return 0; + if (w->getChannels() >= G_DEFAULT_AUDIO_CHANS) + return G_RES_OK; + + unsigned newSize = w->getSize() * G_DEFAULT_AUDIO_CHANS; + + float* newData = new (std::nothrow) float[newSize]; + if (newData == nullptr) { + gu_log("[wfx::monoToStereo] unable to allocate memory!\n"); + return G_RES_ERR_MEMORY; } - for (int i=0, j=0; isize; i++) { - dataNew[j] = w->data[i]; - dataNew[j+1] = w->data[i]; - j+=2; + for (int i=0, k=0; igetSize(); i++, k+=G_DEFAULT_AUDIO_CHANS) { + float* frame = w->getFrame(i); + for (int j=0; jdata); - w->data = dataNew; - w->size = newSize; - w->frames(w->frames()*2); - w->channels(2); + w->free(); + w->setData(newData, newSize); + w->setChannels(G_DEFAULT_AUDIO_CHANS); - return 1; + return G_RES_OK; } /* -------------------------------------------------------------------------- */ -void wfx_silence(Wave *w, int a, int b) +void silence(Wave* w, int a, int b) { - /* stereo values */ - a = a * 2; - b = b * 2; + gu_log("[wfx::silence] silencing from %d to %d\n", a, b); - gu_log("[wfx] silencing from %d to %d\n", a, b); - - for (int i=a; idata[i] = 0.0f; - w->data[i+1] = 0.0f; + for (int i=a; igetFrame(i); + for (int j=0; jgetChannels(); j++) + frame[j] = 0.0f; } - w->isEdited = true; + w->setEdited(true); return; } @@ -106,120 +125,109 @@ void wfx_silence(Wave *w, int a, int b) /* -------------------------------------------------------------------------- */ -int wfx_cut(Wave *w, int a, int b) +int cut(Wave* w, int a, int b) { - a = a * 2; - b = b * 2; - if (a < 0) a = 0; - if (b > w->size) b = w->size; + if (b > w->getSize()) b = w->getSize(); - /* create a new temp wave and copy there the original one, skipping - * the a-b range */ + /* Create a new temp wave and copy there the original one, skipping the a-b + range. */ - unsigned newSize = w->size-(b-a); - float *temp = (float *) malloc(newSize * sizeof(float)); - if (temp == nullptr) { - gu_log("[wfx] unable to allocate memory for cutting\n"); - return 0; + unsigned newSize = (w->getSize() - (b - a)) * w->getChannels(); + float* newData = new (std::nothrow) float[newSize]; + if (newData == nullptr) { + gu_log("[wfx::cut] unable to allocate memory!\n"); + return G_RES_ERR_MEMORY; } - gu_log("[wfx] cutting from %d to %d, new size=%d (video=%d)\n", - a, b, newSize, newSize/2); + gu_log("[wfx::cut] cutting from %d to %d\n", a, b); - for (int i=0, k=0; isize; i++) { - if (i < a || i >= b) { // left margin always included, in order to keep - temp[k] = w->data[i]; // the stereo pair - k++; + for (int i=0, k=0; igetSize(); i++) { + if (i < a || i >= b) { + float* frame = w->getFrame(i); + for (int j=0; jgetChannels(); j++) + newData[k+j] = frame[j]; + k += w->getChannels(); } } - free(w->data); - w->data = temp; - w->size = newSize; - //w->inHeader.frames -= b-a; - w->frames(w->frames() - b - a); - w->isEdited = true; - - gu_log("[wfx] cutting done\n"); + w->free(); + w->setData(newData, newSize); + w->setEdited(true); - return 1; + return G_RES_OK; } /* -------------------------------------------------------------------------- */ -int wfx_trim(Wave *w, int a, int b) +int trim(Wave* w, int a, int b) { - a = a * 2; - b = b * 2; - if (a < 0) a = 0; - if (b > w->size) b = w->size; + if (b > w->getSize()) b = w->getSize(); - int newSize = b - a; - float *temp = (float *) malloc(newSize * sizeof(float)); - if (temp == nullptr) { + int newSize = (b - a) * w->getChannels(); + float* newData = new (std::nothrow) float[newSize]; + if (newData == nullptr) { gu_log("[wfx] unable to allocate memory for trimming\n"); - return 0; + return G_RES_ERR_MEMORY; } - gu_log("[wfx] trimming from %d to %d (area = %d)\n", a, b, b-a); + gu_log("[wfx::trim] trimming from %d to %d (area = %d)\n", a, b, b-a); - for (int i=a, k=0; idata[i]; + for (int i=a, k=0; igetChannels()) { + float* frame = w->getFrame(i); + for (int j=0; jgetChannels(); j++) + newData[k+j] = frame[j]; + } - free(w->data); - w->data = temp; - w->size = newSize; - //w->inHeader.frames = b-a; - w->frames(b - a); - w->isEdited = true; + w->free(); + w->setData(newData, newSize); + w->setEdited(true); - return 1; + return G_RES_OK; } /* -------------------------------------------------------------------------- */ -void wfx_fade(Wave *w, int a, int b, int type) +void fade(Wave* w, int a, int b, int type) { - float m = type == 0 ? 0.0f : 1.0f; - float d = 1.0f/(float)(b-a); - if (type == 1) - d = -d; - - a *= 2; - b *= 2; - - for (int i=a; idata[i] *= m; - w->data[i+1] *= m; - m += d; - } + gu_log("[wfx::fade] fade from %d to %d (range = %d)\n", a, b, b-a); - w->isEdited = true; + float m = 0.0f; + float d = 1.0f / (float) (b - a); + + if (type == FADE_IN) + for (int i=a; i<=b; i++, m+=d) + fadeFrame(w, i, m); + else + for (int i=b; i>=a; i--, m+=d) + fadeFrame(w, i, m); + + w->setEdited(true); } /* -------------------------------------------------------------------------- */ -void wfx_smooth(Wave *w, int a, int b) +void smooth(Wave* w, int a, int b) { - int d = 32; // 64 if stereo data - - /* do nothing if fade edges (both of 32 samples) are > than selected - * portion of wave. d*2 => count both edges, (b-a)*2 => stereo - * values. */ + /* Do nothing if fade edges (both of SMOOTH_SIZE samples) are > than selected + portion of wave. SMOOTH_SIZE*2 to count both edges. */ - if (d*2 > (b-a)*2) { - gu_log("[WFX] selection is too small, nothing to do\n"); + if (SMOOTH_SIZE*2 > (b-a)) { + gu_log("[wfx::smooth] selection is too small, nothing to do\n"); return; } - wfx_fade(w, a, a+d, 0); - wfx_fade(w, b-d, b, 1); + fade(w, a, a+SMOOTH_SIZE, FADE_IN); + fade(w, b-SMOOTH_SIZE, b, FADE_OUT); + + w->setEdited(true); } + +}}}; // giada::m::wfx:: \ No newline at end of file diff --git a/src/core/waveFx.h b/src/core/waveFx.h index 4b7dee8..4360052 100644 --- a/src/core/waveFx.h +++ b/src/core/waveFx.h @@ -2,8 +2,6 @@ * * Giada - Your Hardcore Loopmachine * - * waveFx - * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2017 Giovanni A. Zuliani | Monocasual @@ -34,29 +32,33 @@ class Wave; -/* normalizeSoft - * normalize the wave by returning the dB value for the boost volume. It - * doesn't deal with data in memory. */ - -float wfx_normalizeSoft(Wave *w); - -bool wfx_monoToStereo(Wave *w); +namespace giada { +namespace m { +namespace wfx +{ +static const int FADE_IN = 0; +static const int FADE_OUT = 1; +static const int SMOOTH_SIZE = 32; -void wfx_silence(Wave *w, int a, int b); +/* normalizeSoft +Normalizes the wave by returning the dB value for the boost volume. */ -int wfx_cut(Wave *w, int a, int b); +float normalizeSoft(Wave* w); -int wfx_trim(Wave *w, int a, int b); +int monoToStereo(Wave* w); +void silence(Wave* w, int a, int b); +int cut(Wave* w, int a, int b); +int trim(Wave* w, int a, int b); /* fade * fade in or fade out selection. Fade In = type 0, Fade Out = type 1 */ -void wfx_fade(Wave *w, int a, int b, int type); +void fade(Wave* w, int a, int b, int type); /* smooth * smooth edges of selection. */ -void wfx_smooth(Wave *w, int a, int b); - +void smooth(Wave* w, int a, int b); +}}}; // giada::m::wfx:: #endif diff --git a/src/core/waveManager.cpp b/src/core/waveManager.cpp new file mode 100644 index 0000000..bd102b4 --- /dev/null +++ b/src/core/waveManager.cpp @@ -0,0 +1,222 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2017 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#include +#include +#include +#include "../utils/log.h" +#include "../utils/fs.h" +#include "const.h" +#include "wave.h" +#include "waveFx.h" +#include "waveManager.h" + + +using std::string; + + +namespace giada { +namespace m { +namespace waveManager +{ +namespace +{ +int getBits(SF_INFO& header) +{ + if (header.format & SF_FORMAT_PCM_S8) + return 8; + else if (header.format & SF_FORMAT_PCM_16) + return 16; + else if (header.format & SF_FORMAT_PCM_24) + return 24; + else if (header.format & SF_FORMAT_PCM_32) + return 32; + else if (header.format & SF_FORMAT_PCM_U8) + return 8; + else if (header.format & SF_FORMAT_FLOAT) + return 32; + else if (header.format & SF_FORMAT_DOUBLE) + return 64; + return 0; +} +}; // {anonymous} + + +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ + + +int create(const string& path, Wave** out) +{ + if (path == "" || gu_isDir(path)) { + gu_log("[waveManager::create] malformed path (was '%s')\n", path.c_str()); + return G_RES_ERR_NO_DATA; + } + + if (path.size() > FILENAME_MAX) + return G_RES_ERR_PATH_TOO_LONG; + + SF_INFO header; + SNDFILE* fileIn = sf_open(path.c_str(), SFM_READ, &header); + + if (fileIn == nullptr) { + gu_log("[waveManager::create] unable to read %s. %s\n", path.c_str(), sf_strerror(fileIn)); + return G_RES_ERR_IO; + } + + if (header.channels > 2) { + gu_log("[waveManager::create] unsupported multi-channel sample\n"); + return G_RES_ERR_WRONG_DATA; + } + + /* Libsndfile's frame structure: + + frame1 = [leftChannel, rightChannel] + frame2 = [leftChannel, rightChannel] + ... */ + + int size = header.frames * header.channels; + float* data = new (std::nothrow) float[size]; + if (data == nullptr) { + gu_log("[waveManager::create] unable to allocate memory\n"); + return G_RES_ERR_MEMORY; + } + + if (sf_read_float(fileIn, data, size) != size) + gu_log("[waveManager::create] warning: incomplete read!\n"); + + sf_close(fileIn); + + Wave* wave = new Wave(data, size, header.channels, header.samplerate, + getBits(header), path); + + if (header.channels == 1 && !wfx::monoToStereo(wave)) { + delete wave; + return G_RES_ERR_PROCESSING; + } + + *out = wave; + + gu_log("[waveManager::create] new Wave created, %d frames\n", wave->getSize()); + + return G_RES_OK; +} + + +/* -------------------------------------------------------------------------- */ + + +int createEmpty(int size, int samplerate, const string& name, Wave** out) +{ + float* data = new (std::nothrow) float[size]; + if (data == nullptr) { + gu_log("[waveManager::createEmpty] unable to allocate memory\n"); + return G_RES_ERR_MEMORY; + } + + Wave *wave = new Wave(data, size, 2, samplerate, G_DEFAULT_BIT_DEPTH, ""); + wave->setLogical(true); + wave->setName(name); + wave->setPath(gu_getCurrentPath() + G_SLASH + wave->getName()); + + *out = wave; + + gu_log("[waveManager::createEmpty] new empty Wave created, %d frames\n", size); + + return G_RES_OK; +} + + +/* -------------------------------------------------------------------------- */ + + +int resample(Wave* w, int quality, int samplerate) +{ + float ratio = samplerate / (float) w->getRate(); + int newSizeFrames = ceil(w->getSize() * ratio); + int newSizeSamples = newSizeFrames * w->getChannels(); + + float* newData = new (std::nothrow) float[newSizeSamples]; + if (newData == nullptr) { + gu_log("[waveManager::resample] unable to allocate memory\n"); + return G_RES_ERR_MEMORY; + } + + SRC_DATA src_data; + src_data.data_in = w->getData(); + src_data.input_frames = w->getSize(); + src_data.data_out = newData; + src_data.output_frames = newSizeFrames; + src_data.src_ratio = ratio; + + gu_log("[waveManager::resample] resampling: new size=%d (%d frames)\n", + newSizeSamples, newSizeFrames); + + int ret = src_simple(&src_data, quality, w->getChannels()); + if (ret != 0) { + gu_log("[waveManager::resample] resampling error: %s\n", src_strerror(ret)); + delete[] newData; + return G_RES_ERR_PROCESSING; + } + + w->free(); + w->setData(newData, newSizeSamples); + w->setRate(samplerate); + + return G_RES_OK; +} + + +/* -------------------------------------------------------------------------- */ + + +int save(Wave* w, const string& path) +{ + SF_INFO header; + header.samplerate = w->getRate(); + header.channels = w->getChannels(); + header.format = SF_FORMAT_WAV | SF_FORMAT_FLOAT; + + SNDFILE* file = sf_open(path.c_str(), SFM_WRITE, &header); + if (file == nullptr) { + gu_log("[waveManager::save] unable to open %s for exporting: %s\n", + path.c_str(), sf_strerror(file)); + return G_RES_ERR_IO; + } + + if (sf_writef_float(file, w->getData(), w->getSize()) != w->getSize()) + gu_log("[waveManager::save] warning: incomplete write!\n"); + + sf_close(file); + + w->setLogical(false); + w->setEdited(false); + + return G_RES_OK; +} +}}}; // giada::m::waveManager diff --git a/src/core/waveManager.h b/src/core/waveManager.h new file mode 100644 index 0000000..b9e8a05 --- /dev/null +++ b/src/core/waveManager.h @@ -0,0 +1,57 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2017 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#ifndef G_WAVE_MANAGER_H +#define G_WAVE_MANAGER_H + + +#include + + +class Wave; + + +namespace giada { +namespace m { +namespace waveManager +{ +/* create +Creates a new Wave object with data read from file 'path'. */ + +int create(const std::string& path, Wave** out); + +/* createEmpty +Creates a new silent Wave object. Note: 'size' must take 2 channels into account +(stereo). */ + +int createEmpty(int size, int samplerate, const std::string& name, Wave** out); +int resample(Wave* w, int quality, int samplerate); +int save(Wave* w, const std::string& path); + +}}}; // giada::m::waveManager + +#endif \ No newline at end of file diff --git a/src/glue/channel.cpp b/src/glue/channel.cpp index eec216b..d9c13d5 100644 --- a/src/glue/channel.cpp +++ b/src/glue/channel.cpp @@ -45,6 +45,7 @@ #include "../gui/elems/mainWindow/keyboard/channelButton.h" #include "../utils/gui.h" #include "../utils/fs.h" +#include "../utils/log.h" #include "../core/kernelAudio.h" #include "../core/mixerHandler.h" #include "../core/mixer.h" @@ -56,11 +57,12 @@ #include "../core/sampleChannel.h" #include "../core/midiChannel.h" #include "../core/plugin.h" +#include "../core/waveManager.h" #include "main.h" #include "channel.h" -extern gdMainWindow *G_MainWin; +extern gdMainWindow* G_MainWin; using std::string; @@ -70,7 +72,7 @@ using namespace giada::m; static bool __soloSession__ = false; -int glue_loadChannel(SampleChannel *ch, const string &fname) +int glue_loadChannel(SampleChannel* ch, const string& fname) { /* Always stop a channel before loading a new sample in it. This will prevent issues if tracker is outside the boundaries of the new sample -> segfault. */ @@ -82,10 +84,24 @@ int glue_loadChannel(SampleChannel *ch, const string &fname) conf::samplePath = gu_dirname(fname); - int result = ch->load(fname.c_str(), conf::samplerate, conf::rsmpQuality); + Wave* wave = nullptr; + int result = waveManager::create(fname, &wave); + if (result != G_RES_OK) + return result; + + if (wave->getRate() != conf::samplerate) { + gu_log("[glue_loadChannel] input rate (%d) != system rate (%d), conversion needed\n", + wave->getRate(), conf::samplerate); + result = waveManager::resample(wave, conf::rsmpQuality, conf::samplerate); + if (result != G_RES_OK) { + delete wave; + return result; + } + } - if (result == SAMPLE_LOADED_OK) - G_MainWin->keyboard->updateChannel(ch->guiChannel); + ch->pushWave(wave); + + G_MainWin->keyboard->updateChannel(ch->guiChannel); return result; } @@ -94,10 +110,10 @@ int glue_loadChannel(SampleChannel *ch, const string &fname) /* -------------------------------------------------------------------------- */ -Channel *glue_addChannel(int column, int type) +Channel* glue_addChannel(int column, int type) { - Channel *ch = mh::addChannel(type); - geChannel *gch = G_MainWin->keyboard->addChannel(column, ch); + Channel* ch = mh::addChannel(type); + geChannel* gch = G_MainWin->keyboard->addChannel(column, ch); ch->guiChannel = gch; return ch; } @@ -106,7 +122,7 @@ Channel *glue_addChannel(int column, int type) /* -------------------------------------------------------------------------- */ -void glue_deleteChannel(Channel *ch) +void glue_deleteChannel(Channel* ch) { if (!gdConfirmWin("Warning", "Delete channel: are you sure?")) return; diff --git a/src/glue/main.cpp b/src/glue/main.cpp index 3abaa58..efc8c67 100644 --- a/src/glue/main.cpp +++ b/src/glue/main.cpp @@ -100,7 +100,7 @@ void glue_setBpm(float v) double fPpart = modf(v, &fIpart); int iIpart = fIpart; int iPpart = ceilf(fPpart); - glue_setBpm(gu_itoa(iIpart).c_str(), gu_itoa(iPpart).c_str()); + glue_setBpm(gu_toString(iIpart).c_str(), gu_toString(iPpart).c_str()); } @@ -223,7 +223,9 @@ void glue_clearAllRecs() void glue_resetToInitState(bool resetGui, bool createColumns) { + gu_closeAllSubwindows(); mixer::close(); + clock::init(conf::samplerate, conf::midiTCfps); mixer::init(clock::getTotalFrames(), kernelAudio::getRealBufSize()); recorder::init(); #ifdef WITH_VST diff --git a/src/glue/sampleEditor.cpp b/src/glue/sampleEditor.cpp index fbbb7ef..ecaade6 100644 --- a/src/glue/sampleEditor.cpp +++ b/src/glue/sampleEditor.cpp @@ -30,6 +30,7 @@ #include "../gui/dialogs/gd_mainWindow.h" #include "../gui/dialogs/sampleEditor.h" #include "../gui/dialogs/gd_warnings.h" +#include "../gui/elems/basics/button.h" #include "../gui/elems/sampleEditor/waveTools.h" #include "../gui/elems/sampleEditor/volumeTool.h" #include "../gui/elems/sampleEditor/boostTool.h" @@ -62,6 +63,7 @@ gdSampleEditor* getSampleEditorWindow() return se; } + /* -------------------------------------------------------------------------- */ @@ -81,14 +83,15 @@ void setBeginEndChannel(SampleChannel* ch, int b, int e) void cut(SampleChannel* ch, int a, int b) { - if (!wfx_cut(ch->wave, a, b)) { + if (!wfx::cut(ch->wave, a, b)) { gdAlert("Unable to cut the sample!"); return; } - setBeginEndChannel(ch, ch->begin, ch->end); + setBeginEndChannel(ch, ch->getBegin(), ch->getEnd()); gdSampleEditor* gdEditor = getSampleEditorWindow(); - gdEditor->waveTools->waveform->clearSel(); - gdEditor->waveTools->waveform->refresh(); + gdEditor->waveTools->waveform->clearSel(); + gdEditor->waveTools->waveform->refresh(); + gdEditor->updateInfo(); } @@ -97,7 +100,7 @@ void cut(SampleChannel* ch, int a, int b) void silence(SampleChannel* ch, int a, int b) { - wfx_silence(ch->wave, a, b); + wfx::silence(ch->wave, a, b); gdSampleEditor* gdEditor = getSampleEditorWindow(); gdEditor->waveTools->waveform->refresh(); } @@ -108,7 +111,7 @@ void silence(SampleChannel* ch, int a, int b) void fade(SampleChannel* ch, int a, int b, int type) { - wfx_fade(ch->wave, a, b, type); + wfx::fade(ch->wave, a, b, type); gdSampleEditor* gdEditor = getSampleEditorWindow(); gdEditor->waveTools->waveform->refresh(); } @@ -119,7 +122,7 @@ void fade(SampleChannel* ch, int a, int b, int type) void smoothEdges(SampleChannel* ch, int a, int b) { - wfx_smooth(ch->wave, a, b); + wfx::smooth(ch->wave, a, b); gdSampleEditor* gdEditor = getSampleEditorWindow(); gdEditor->waveTools->waveform->refresh(); } @@ -130,7 +133,7 @@ void smoothEdges(SampleChannel* ch, int a, int b) void setStartEnd(SampleChannel* ch, int a, int b) { - setBeginEndChannel(ch, a * 2, b * 2); // stereo values + setBeginEndChannel(ch, a, b); gdSampleEditor* gdEditor = getSampleEditorWindow(); gdEditor->waveTools->waveform->recalcPoints(); gdEditor->waveTools->waveform->clearSel(); @@ -143,14 +146,49 @@ void setStartEnd(SampleChannel* ch, int a, int b) void trim(SampleChannel* ch, int a, int b) { - if (!wfx_trim(ch->wave, a, b)) { + if (!wfx::trim(ch->wave, a, b)) { gdAlert("Unable to trim the sample!"); return; } - setBeginEndChannel(ch, ch->begin, ch->end); + setBeginEndChannel(ch, ch->getBegin(), ch->getEnd()); + gdSampleEditor* gdEditor = getSampleEditorWindow(); + gdEditor->waveTools->waveform->clearSel(); + gdEditor->waveTools->waveform->refresh(); + gdEditor->updateInfo(); +} + + +/* -------------------------------------------------------------------------- */ + + +void setPlayHead(SampleChannel* ch, int f) +{ + ch->setTrackerPreview(f); + gdSampleEditor* gdEditor = getSampleEditorWindow(); + gdEditor->waveTools->waveform->redraw(); +} + + +/* -------------------------------------------------------------------------- */ + + +void setPreview(SampleChannel* ch, int mode) +{ + ch->setPreviewMode(mode); gdSampleEditor* gdEditor = getSampleEditorWindow(); - gdEditor->waveTools->waveform->clearSel(); - gdEditor->waveTools->waveform->refresh(); + gdEditor->play->value(!gdEditor->play->value()); } + +/* -------------------------------------------------------------------------- */ + + +void rewindPreview(SampleChannel* ch) +{ + geWaveform* waveform = getSampleEditorWindow()->waveTools->waveform; + if (waveform->isSelected() && ch->getTrackerPreview() != waveform->getSelectionA()) + setPlayHead(ch, waveform->getSelectionA()); + else + setPlayHead(ch, 0); +} }}}; // giada::c::sampleEditor:: diff --git a/src/glue/sampleEditor.h b/src/glue/sampleEditor.h index 63babff..e4feeec 100644 --- a/src/glue/sampleEditor.h +++ b/src/glue/sampleEditor.h @@ -51,6 +51,13 @@ void fade(SampleChannel* ch, int a, int b, int type); void smoothEdges(SampleChannel* ch, int a, int b); void setStartEnd(SampleChannel* ch, int a, int b); +/* setPlayHead +Changes playhead's position. Used in preview. */ + +void setPlayHead(SampleChannel* ch, int f); + +void setPreview(SampleChannel* ch, int mode); +void rewindPreview(SampleChannel* ch); }}}; // giada::c::sampleEditor:: #endif diff --git a/src/glue/storage.cpp b/src/glue/storage.cpp index 61a3c39..c853a9b 100644 --- a/src/glue/storage.cpp +++ b/src/glue/storage.cpp @@ -329,12 +329,12 @@ void glue_saveProject(void *data) /* update the new samplePath: everything now comes from the project * folder (folderPath). Also remove any existing file. */ - string samplePath = fullPath + G_SLASH + ch->wave->basename(true); + string samplePath = fullPath + G_SLASH + ch->wave->getBasename(true); if (gu_fileExists(samplePath)) remove(samplePath.c_str()); if (ch->save(samplePath.c_str())) - ch->wave->pathfile = samplePath; + ch->wave->setPath(samplePath); } string gptcPath = fullPath + G_SLASH + gu_stripExt(name) + ".gptc"; @@ -358,7 +358,7 @@ void glue_loadSample(void *data) int res = glue_loadChannel((SampleChannel*) browser->getChannel(), fullPath.c_str()); - if (res == SAMPLE_LOADED_OK) { + if (res == G_RES_OK) { conf::samplePath = gu_dirname(fullPath); browser->do_callback(); G_MainWin->delSubWindow(WID_SAMPLE_EDITOR); // if editor is open diff --git a/src/gui/dialogs/gd_about.cpp b/src/gui/dialogs/gd_about.cpp index dd416b6..527e32c 100644 --- a/src/gui/dialogs/gd_about.cpp +++ b/src/gui/dialogs/gd_about.cpp @@ -48,12 +48,10 @@ using namespace giada::u; gdAbout::gdAbout() #ifdef WITH_VST : gdWindow(340, 435, "About Giada") -{ #else : gdWindow(340, 350, "About Giada") -{ #endif - +{ if (conf::aboutX) resize(conf::aboutX, conf::aboutY, w(), h()); @@ -75,7 +73,7 @@ gdAbout::gdAbout() sprintf( message, "Version " G_VERSION_STR " (" BUILD_DATE ")\n\n" - "Developed by Monocasual\n" + "Developed by Monocasual Laboratories\n" "Based on FLTK (%d.%d.%d), RtAudio (%s),\n" "RtMidi (%s), Libsamplerate, Jansson (%s),\n" "Libsndfile (%s)" @@ -93,10 +91,9 @@ gdAbout::gdAbout() deps::getRtMidiVersion().c_str(), JANSSON_VERSION, deps::getLibsndfileVersion().c_str() #ifdef WITH_VST - , JUCE_MAJOR_VERSION, JUCE_MINOR_VERSION, JUCE_BUILDNUMBER); -#else - ); + , JUCE_MAJOR_VERSION, JUCE_MINOR_VERSION, JUCE_BUILDNUMBER #endif + ); int tw = 0; int th = 0; diff --git a/src/gui/dialogs/gd_devInfo.cpp b/src/gui/dialogs/gd_devInfo.cpp index 59b1f4e..c7c074d 100644 --- a/src/gui/dialogs/gd_devInfo.cpp +++ b/src/gui/dialogs/gd_devInfo.cpp @@ -52,21 +52,21 @@ gdDevInfo::gdDevInfo(unsigned dev) int lines = 7; body = "Device name: " + kernelAudio::getDeviceName(dev) + "\n"; - body += "Total output(s): " + gu_itoa(kernelAudio::getMaxOutChans(dev)) + "\n"; - body += "Total intput(s): " + gu_itoa(kernelAudio::getMaxInChans(dev)) + "\n"; - body += "Duplex channel(s): " + gu_itoa(kernelAudio::getDuplexChans(dev)) + "\n"; + body += "Total output(s): " + gu_toString(kernelAudio::getMaxOutChans(dev)) + "\n"; + body += "Total intput(s): " + gu_toString(kernelAudio::getMaxInChans(dev)) + "\n"; + body += "Duplex channel(s): " + gu_toString(kernelAudio::getDuplexChans(dev)) + "\n"; body += "Default output: " + string(kernelAudio::isDefaultOut(dev) ? "yes" : "no") + "\n"; body += "Default input: " + string(kernelAudio::isDefaultIn(dev) ? "yes" : "no") + "\n"; int totalFreq = kernelAudio::getTotalFreqs(dev); - body += "Supported frequencies: " + gu_itoa(totalFreq); + body += "Supported frequencies: " + gu_toString(totalFreq); for (int i=0; icopy_label(body.c_str()); diff --git a/src/gui/dialogs/gd_pluginList.cpp b/src/gui/dialogs/gd_pluginList.cpp index 0632852..edb8de7 100644 --- a/src/gui/dialogs/gd_pluginList.cpp +++ b/src/gui/dialogs/gd_pluginList.cpp @@ -89,7 +89,7 @@ gdPluginList::gdPluginList(int stackType, Channel *ch) if (stackType == pluginHost::MASTER_IN) label("Master In Plugins"); else { - string l = "Channel " + gu_itoa(ch->index+1) + " Plugins"; + string l = "Channel " + gu_toString(ch->index+1) + " Plugins"; copy_label(l.c_str()); } diff --git a/src/gui/dialogs/midiIO/midiInputChannel.cpp b/src/gui/dialogs/midiIO/midiInputChannel.cpp index 224d403..2f02c2e 100644 --- a/src/gui/dialogs/midiIO/midiInputChannel.cpp +++ b/src/gui/dialogs/midiIO/midiInputChannel.cpp @@ -53,7 +53,7 @@ gdMidiInputChannel::gdMidiInputChannel(Channel *ch) conf::midiInputH, "MIDI Input Setup"), ch(ch) { - string title = "MIDI Input Setup (channel " + gu_itoa(ch->index+1) + ")"; + string title = "MIDI Input Setup (channel " + gu_toString(ch->index+1) + ")"; label(title.c_str()); size_range(G_DEFAULT_MIDI_INPUT_UI_W, G_DEFAULT_MIDI_INPUT_UI_H); diff --git a/src/gui/dialogs/sampleEditor.cpp b/src/gui/dialogs/sampleEditor.cpp index 1da61ab..0d8b474 100644 --- a/src/gui/dialogs/sampleEditor.cpp +++ b/src/gui/dialogs/sampleEditor.cpp @@ -38,7 +38,6 @@ #include "../../core/sampleChannel.h" #include "../../core/mixer.h" #include "../../core/wave.h" -#include "../../core/clock.h" #include "../../utils/gui.h" #include "../../utils/string.h" #include "../elems/basics/button.h" @@ -59,60 +58,72 @@ #include "sampleEditor.h" +using std::string; using namespace giada::m; using namespace giada::c; -gdSampleEditor::gdSampleEditor(SampleChannel *ch) +gdSampleEditor::gdSampleEditor(SampleChannel* ch) : gdWindow(640, 480), ch(ch) { - begin(); + Fl_Group* upperBar = createUpperBar(); + + waveTools = new geWaveTools(8, upperBar->y()+upperBar->h()+8, w()-16, h()-128, + ch); + + Fl_Group* bottomBar = createBottomBar(8, waveTools->y()+waveTools->h()+8, + h()-waveTools->h()-upperBar->h()-32); + + add(upperBar); + add(waveTools); + add(bottomBar); - /* top bar: grid and zoom tools */ + resizable(waveTools); - Fl_Group *bar = new Fl_Group(8, 8, w()-16, 20); - bar->begin(); - grid = new geChoice(bar->x(), bar->y(), 50, 20); - snap = new geCheck(grid->x()+grid->w()+4, bar->y(), 12, 12); - sep1 = new geBox(snap->x()+snap->w()+4, bar->y(), 506, 20); - zoomOut = new geButton(sep1->x()+sep1->w()+4, bar->y(), 20, 20, "", zoomOutOff_xpm, zoomOutOn_xpm); - zoomIn = new geButton(zoomOut->x()+zoomOut->w()+4, bar->y(), 20, 20, "", zoomInOff_xpm, zoomInOn_xpm); - bar->end(); - bar->resizable(sep1); + gu_setFavicon(this); + set_non_modal(); + label(ch->wave->getName().c_str()); - /* waveform */ + size_range(720, 480); + if (conf::sampleEditorX) + resize(conf::sampleEditorX, conf::sampleEditorY, conf::sampleEditorW, + conf::sampleEditorH); + + show(); +} - waveTools = new geWaveTools(8, bar->y()+bar->h()+8, w()-16, h()-128, ch); - waveTools->end(); - /* other tools */ - Fl_Group *row1 = new Fl_Group(8, waveTools->y()+waveTools->h()+8, w()-16, 20); - row1->begin(); - volumeTool = new geVolumeTool(row1->x(), row1->y(), ch); - boostTool = new geBoostTool(volumeTool->x()+volumeTool->w()+4, row1->y(), ch); - panTool = new gePanTool(boostTool->x()+boostTool->w()+4, row1->y(), ch); - row1->end(); - row1->resizable(0); +/* -------------------------------------------------------------------------- */ - Fl_Group *row2 = new Fl_Group(8, row1->y()+row1->h()+8, 800, 20); - row2->begin(); - pitchTool = new gePitchTool(row2->x(), row2->y(), ch); - row2->end(); - row2->resizable(0); - Fl_Group *row3 = new Fl_Group(8, row2->y()+row2->h()+8, w()-16, 20); - row3->begin(); - rangeTool = new geRangeTool(row3->x(), row3->y(), ch); - sep2 = new geBox(rangeTool->x()+rangeTool->w()+4, row3->y(), 246, 20); - reload = new geButton(sep2->x()+sep2->w()+4, row3->y(), 70, 20, "Reload"); - row3->end(); - row3->resizable(sep2); +gdSampleEditor::~gdSampleEditor() +{ + conf::sampleEditorX = x(); + conf::sampleEditorY = y(); + conf::sampleEditorW = w(); + conf::sampleEditorH = h(); + conf::sampleEditorGridVal = atoi(grid->text()); + conf::sampleEditorGridOn = snap->value(); + sampleEditor::setPreview(ch, G_PREVIEW_NONE); +} - end(); - /* grid tool setup */ +/* -------------------------------------------------------------------------- */ + + +Fl_Group* gdSampleEditor::createUpperBar() +{ + Fl_Group* g = new Fl_Group(8, 8, w()-16, 20); + g->begin(); + grid = new geChoice(g->x(), g->y(), 50, 20); + snap = new geCheck(grid->x()+grid->w()+4, g->y()+3, 12, 12, "Snap"); + sep1 = new geBox(snap->x()+snap->w()+4, g->y(), 506, 20); + zoomOut = new geButton(sep1->x()+sep1->w()+4, g->y(), 20, 20, "", zoomOutOff_xpm, zoomOutOn_xpm); + zoomIn = new geButton(zoomOut->x()+zoomOut->w()+4, g->y(), 20, 20, "", zoomInOff_xpm, zoomInOn_xpm); + g->end(); + g->resizable(sep1); grid->add("(off)"); grid->add("2"); @@ -126,7 +137,7 @@ gdSampleEditor::gdSampleEditor(SampleChannel *ch) if (conf::sampleEditorGridVal == 0) grid->value(0); else - grid->value(grid->find_item(gu_itoa(conf::sampleEditorGridVal).c_str())); + grid->value(grid->find_item(gu_toString(conf::sampleEditorGridVal).c_str())); grid->callback(cb_changeGrid, (void*)this); snap->value(conf::sampleEditorGridOn); @@ -134,53 +145,117 @@ gdSampleEditor::gdSampleEditor(SampleChannel *ch) /* TODO - redraw grid if != (off) */ - reload->callback(cb_reload, (void*)this); - zoomOut->callback(cb_zoomOut, (void*)this); zoomIn->callback(cb_zoomIn, (void*)this); - /* logical samples (aka takes) cannot be reloaded. So far. */ + return g; +} + + +/* -------------------------------------------------------------------------- */ + - if (ch->wave->isLogical) +Fl_Group* gdSampleEditor::createOpTools(int x, int y, int h) +{ + Fl_Group* g = new Fl_Group(x, y, 572, h); + g->begin(); + g->resizable(0); + volumeTool = new geVolumeTool(g->x(), g->y(), ch); + boostTool = new geBoostTool(volumeTool->x()+volumeTool->w()+4, g->y(), ch); + panTool = new gePanTool(boostTool->x()+boostTool->w()+4, g->y(), ch); + + pitchTool = new gePitchTool(g->x(), panTool->y()+panTool->h()+8, ch); + + rangeTool = new geRangeTool(g->x(), pitchTool->y()+pitchTool->h()+8, ch); + reload = new geButton(g->x()+g->w()-70, rangeTool->y(), 70, 20, "Reload"); + g->end(); + + if (ch->wave->isLogical()) // Logical samples (aka takes) cannot be reloaded. reload->deactivate(); - gu_setFavicon(this); - size_range(640, 480); - resizable(waveTools); + reload->callback(cb_reload, (void*)this); - label(ch->wave->name.c_str()); + return g; +} - set_non_modal(); - if (conf::sampleEditorX) - resize(conf::sampleEditorX, conf::sampleEditorY, conf::sampleEditorW, conf::sampleEditorH); +/* -------------------------------------------------------------------------- */ - show(); + +Fl_Group* gdSampleEditor::createPreviewBox(int x, int y, int h) +{ + Fl_Group* g = new Fl_Group(x, y, 110, h); + g->begin(); + rewind = new geButton(g->x(), g->y()+(g->h()/2)-12, 25, 25, "", rewindOff_xpm, rewindOn_xpm); + play = new geButton(rewind->x()+rewind->w()+4, g->y()+(g->h()/2)-12, 25, 25, "", play_xpm, pause_xpm); + loop = new geCheck(play->x()+play->w()+6, g->y()+(g->h()/2)-6, 12, 12, "Loop"); + g->end(); + + play->callback(cb_togglePreview, (void*)this); + rewind->callback(cb_rewindPreview, (void*)this); + + ch->setOnEndPreviewCb([this] { + play->value(0); + }); + + return g; } /* -------------------------------------------------------------------------- */ -gdSampleEditor::~gdSampleEditor() +Fl_Group* gdSampleEditor::createInfoBox(int x, int y, int h) { - conf::sampleEditorX = x(); - conf::sampleEditorY = y(); - conf::sampleEditorW = w(); - conf::sampleEditorH = h(); - conf::sampleEditorGridVal = atoi(grid->text()); - conf::sampleEditorGridOn = snap->value(); + Fl_Group* g = new Fl_Group(x, y, 400, h); + g->begin(); + info = new geBox(g->x(), g->y(), g->w(), g->h()); + g->end(); + + info->align(FL_ALIGN_LEFT | FL_ALIGN_INSIDE | FL_ALIGN_TOP); + + updateInfo(); + + return g; +} + + +/* -------------------------------------------------------------------------- */ + + +Fl_Group* gdSampleEditor::createBottomBar(int x, int y, int h) +{ + Fl_Group* g = new Fl_Group(8, waveTools->y()+waveTools->h()+8, w()-16, h); + g->begin(); + Fl_Group* previewBox = createPreviewBox(g->x(), g->y(), g->h()); + + geBox* divisor1 = new geBox(previewBox->x()+previewBox->w()+8, g->y(), 1, g->h()); + divisor1->box(FL_BORDER_BOX); + + Fl_Group* opTools = createOpTools(divisor1->x()+divisor1->w()+12, g->y(), g->h()); + + geBox* divisor2 = new geBox(opTools->x()+opTools->w()+8, g->y(), 1, g->h()); + divisor2->box(FL_BORDER_BOX); + + createInfoBox(divisor2->x()+divisor2->w()+8, g->y(), g->h()); + + g->end(); + g->resizable(0); + + return g; } /* -------------------------------------------------------------------------- */ -void gdSampleEditor::cb_reload (Fl_Widget *w, void *p) { ((gdSampleEditor*)p)->__cb_reload(); } -void gdSampleEditor::cb_zoomIn (Fl_Widget *w, void *p) { ((gdSampleEditor*)p)->__cb_zoomIn(); } -void gdSampleEditor::cb_zoomOut (Fl_Widget *w, void *p) { ((gdSampleEditor*)p)->__cb_zoomOut(); } -void gdSampleEditor::cb_changeGrid(Fl_Widget *w, void *p) { ((gdSampleEditor*)p)->__cb_changeGrid(); } -void gdSampleEditor::cb_enableSnap(Fl_Widget *w, void *p) { ((gdSampleEditor*)p)->__cb_enableSnap(); } +void gdSampleEditor::cb_reload (Fl_Widget* w, void* p) { ((gdSampleEditor*)p)->__cb_reload(); } +void gdSampleEditor::cb_zoomIn (Fl_Widget* w, void* p) { ((gdSampleEditor*)p)->__cb_zoomIn(); } +void gdSampleEditor::cb_zoomOut (Fl_Widget* w, void* p) { ((gdSampleEditor*)p)->__cb_zoomOut(); } +void gdSampleEditor::cb_changeGrid (Fl_Widget* w, void* p) { ((gdSampleEditor*)p)->__cb_changeGrid(); } +void gdSampleEditor::cb_enableSnap (Fl_Widget* w, void* p) { ((gdSampleEditor*)p)->__cb_enableSnap(); } +void gdSampleEditor::cb_togglePreview(Fl_Widget* w, void* p) { ((gdSampleEditor*)p)->__cb_togglePreview(); } +void gdSampleEditor::cb_rewindPreview(Fl_Widget* w, void* p) { ((gdSampleEditor*)p)->__cb_rewindPreview(); } /* -------------------------------------------------------------------------- */ @@ -195,18 +270,35 @@ void gdSampleEditor::__cb_enableSnap() /* -------------------------------------------------------------------------- */ +void gdSampleEditor::__cb_togglePreview() +{ + if (play->value()) + sampleEditor::setPreview(ch, G_PREVIEW_NONE); + else + sampleEditor::setPreview(ch, loop->value() ? G_PREVIEW_LOOP : G_PREVIEW_NORMAL); +} + + +void gdSampleEditor::__cb_rewindPreview() +{ + sampleEditor::rewindPreview(ch); +} + + +/* -------------------------------------------------------------------------- */ + + void gdSampleEditor::__cb_reload() { if (!gdConfirmWin("Warning", "Reload sample: are you sure?")) return; - /* no need for glue_loadChan, there's no gui to refresh */ - - ch->load(ch->wave->pathfile.c_str(), conf::samplerate, conf::rsmpQuality); + if (glue_loadChannel(ch, ch->wave->getPath()) != G_RES_OK) + return; glue_setBoost(ch, G_DEFAULT_BOOST); glue_setPitch(ch, G_DEFAULT_PITCH); - glue_setPanning(ch, 1.0f); + glue_setPanning(ch, 0.5f); panTool->refresh(); boostTool->refresh(); @@ -214,7 +306,7 @@ void gdSampleEditor::__cb_reload() waveTools->waveform->stretchToWindow(); waveTools->updateWaveform(); - sampleEditor::setBeginEndChannel(ch, 0, ch->wave->size); + sampleEditor::setBeginEndChannel(ch, 0, ch->wave->getSize()); redraw(); } @@ -247,3 +339,19 @@ void gdSampleEditor::__cb_changeGrid() { waveTools->waveform->setGridLevel(atoi(grid->text())); } + + +/* -------------------------------------------------------------------------- */ + + +void gdSampleEditor::updateInfo() +{ + string bitDepth = ch->wave->getBits() != 0 ? gu_toString(ch->wave->getBits()) : "(unknown)"; + string infoText = + "File: " + ch->wave->getPath() + "\n" + "Size: " + gu_toString(ch->wave->getSize()) + " frames\n" + "Duration: " + gu_toString(ch->wave->getDuration()) + " seconds\n" + "Bit depth: " + bitDepth + "\n" + "Frequency: " + gu_toString(ch->wave->getRate()) + " Hz\n"; + info->copy_label(infoText.c_str()); +} diff --git a/src/gui/dialogs/sampleEditor.h b/src/gui/dialogs/sampleEditor.h index d0fca54..dc09694 100644 --- a/src/gui/dialogs/sampleEditor.h +++ b/src/gui/dialogs/sampleEditor.h @@ -48,43 +48,62 @@ class geButton; class gdSampleEditor : public gdWindow { +friend class geWaveform; + private: - static void cb_reload (Fl_Widget *w, void *p); - static void cb_zoomIn (Fl_Widget *w, void *p); - static void cb_zoomOut (Fl_Widget *w, void *p); - static void cb_changeGrid (Fl_Widget *w, void *p); - static void cb_enableSnap (Fl_Widget *w, void *p); - inline void __cb_reload(); - inline void __cb_zoomIn(); - inline void __cb_zoomOut(); - inline void __cb_changeGrid(); - inline void __cb_enableSnap(); + Fl_Group* createUpperBar(); + Fl_Group* createBottomBar(int x, int y, int h); + + Fl_Group* createPreviewBox(int x, int y, int h); + Fl_Group* createOpTools(int x, int y, int h); + Fl_Group* createInfoBox(int x, int y, int h); + + static void cb_reload (Fl_Widget* w, void* p); + static void cb_zoomIn (Fl_Widget* w, void* p); + static void cb_zoomOut (Fl_Widget* w, void* p); + static void cb_changeGrid(Fl_Widget* w, void* p); + static void cb_enableSnap(Fl_Widget* w, void* p); + static void cb_togglePreview(Fl_Widget* w, void* p); + static void cb_rewindPreview(Fl_Widget* w, void* p); + void __cb_reload(); + void __cb_zoomIn(); + void __cb_zoomOut(); + void __cb_changeGrid(); + void __cb_enableSnap(); + void __cb_togglePreview(); + void __cb_rewindPreview(); public: - gdSampleEditor(SampleChannel *ch); + gdSampleEditor(SampleChannel* ch); ~gdSampleEditor(); - geChoice *grid; - geCheck *snap; - geBox *sep1; - geButton *zoomIn; - geButton *zoomOut; + void updateInfo(); + + geChoice* grid; + geCheck* snap; + geBox* sep1; + geButton* zoomIn; + geButton* zoomOut; - geWaveTools *waveTools; + geWaveTools* waveTools; + + geVolumeTool* volumeTool; + geBoostTool* boostTool; + gePanTool* panTool; - geVolumeTool *volumeTool; - geBoostTool *boostTool; - gePanTool *panTool; + gePitchTool* pitchTool; - gePitchTool *pitchTool; + geRangeTool* rangeTool; + geButton* reload; - geRangeTool *rangeTool; - geBox *sep2; - geButton *reload; + geButton* play; + geButton* rewind; + geCheck* loop; + geBox* info; - SampleChannel *ch; + SampleChannel* ch; }; diff --git a/src/gui/elems/config/tabAudio.cpp b/src/gui/elems/config/tabAudio.cpp index 6049acd..ae37e9f 100644 --- a/src/gui/elems/config/tabAudio.cpp +++ b/src/gui/elems/config/tabAudio.cpp @@ -154,7 +154,7 @@ geTabAudio::geTabAudio(int X, int Y, int W, int H) int nfreq = kernelAudio::getTotalFreqs(sounddevOut->value()); for (int i=0; ivalue(), i); - samplerate->add(gu_itoa(freq).c_str()); + samplerate->add(gu_toString(freq).c_str()); if (freq == conf::samplerate) samplerate->value(i); } @@ -179,7 +179,7 @@ geTabAudio::geTabAudio(int X, int Y, int W, int H) buffersize->add("1024"); buffersize->add("2048"); buffersize->add("4096"); - buffersize->showItem(gu_itoa(conf::buffersize).c_str()); + buffersize->showItem(gu_toString(conf::buffersize).c_str()); rsmpQuality->add("Sinc best quality (very slow)"); rsmpQuality->add("Sinc medium quality (slow)"); @@ -188,7 +188,7 @@ geTabAudio::geTabAudio(int X, int Y, int W, int H) rsmpQuality->add("Linear (very fast)"); rsmpQuality->value(conf::rsmpQuality); - delayComp->value(gu_itoa(conf::delayComp).c_str()); + delayComp->value(gu_toString(conf::delayComp).c_str()); delayComp->type(FL_INT_INPUT); delayComp->maximum_size(5); diff --git a/src/gui/elems/config/tabPlugins.cpp b/src/gui/elems/config/tabPlugins.cpp index 33b2225..ee9b77e 100644 --- a/src/gui/elems/config/tabPlugins.cpp +++ b/src/gui/elems/config/tabPlugins.cpp @@ -74,7 +74,7 @@ geTabPlugins::geTabPlugins(int X, int Y, int W, int H) void geTabPlugins::updateCount() { - string scanLabel = "Scan (" + gu_itoa(pluginHost::countAvailablePlugins()) + " found)"; + string scanLabel = "Scan (" + gu_toString(pluginHost::countAvailablePlugins()) + " found)"; scanButton->label(scanLabel.c_str()); } @@ -90,7 +90,7 @@ void geTabPlugins::cb_scan(Fl_Widget *w, void *p) { ((geTabPlugins*)p)->__cb_sca void geTabPlugins::cb_onScan(float progress, void *p) { - string l = "Scan in progress (" + gu_itoa((int)(progress*100)) + "%). Please wait..."; + string l = "Scan in progress (" + gu_toString((int)(progress*100)) + "%). Please wait..."; ((geTabPlugins *)p)->info->label(l.c_str()); Fl::wait(); } diff --git a/src/gui/elems/mainWindow/keyboard/channelStatus.cpp b/src/gui/elems/mainWindow/keyboard/channelStatus.cpp index a9b3fc7..ff94d68 100644 --- a/src/gui/elems/mainWindow/keyboard/channelStatus.cpp +++ b/src/gui/elems/mainWindow/keyboard/channelStatus.cpp @@ -76,7 +76,7 @@ void geChannelStatus::draw() if (pos == -1) pos = 0; else - pos = (pos * (w()-1)) / (ch->end - ch->begin); + pos = (pos * (w()-1)) / ((ch->getEnd() - ch->getBegin())); fl_rectf(x()+1, y()+1, pos, h()-2, G_COLOR_LIGHT_1); } } diff --git a/src/gui/elems/mainWindow/keyboard/column.cpp b/src/gui/elems/mainWindow/keyboard/column.cpp index 6293ae3..111ad44 100644 --- a/src/gui/elems/mainWindow/keyboard/column.cpp +++ b/src/gui/elems/mainWindow/keyboard/column.cpp @@ -107,7 +107,7 @@ int geColumn::handle(int e) gu_log("[geColumn::handle] loading %s...\n", paths.at(i).c_str()); SampleChannel *c = (SampleChannel*) glue_addChannel(index, CHANNEL_SAMPLE); result = glue_loadChannel(c, gu_stripFileUrl(paths.at(i)).c_str()); - if (result != SAMPLE_LOADED_OK) { + if (result != G_RES_OK) { deleteChannel(c->guiChannel); fails = true; } diff --git a/src/gui/elems/mainWindow/keyboard/keyboard.cpp b/src/gui/elems/mainWindow/keyboard/keyboard.cpp index b423196..f76b216 100644 --- a/src/gui/elems/mainWindow/keyboard/keyboard.cpp +++ b/src/gui/elems/mainWindow/keyboard/keyboard.cpp @@ -88,7 +88,7 @@ void geKeyboard::init() /* -------------------------------------------------------------------------- */ -void geKeyboard::freeChannel(geChannel *gch) +void geKeyboard::freeChannel(geChannel* gch) { gch->reset(); } @@ -97,7 +97,7 @@ void geKeyboard::freeChannel(geChannel *gch) /* -------------------------------------------------------------------------- */ -void geKeyboard::deleteChannel(geChannel *gch) +void geKeyboard::deleteChannel(geChannel* gch) { for (unsigned i=0; ifind(gch); @@ -112,7 +112,7 @@ void geKeyboard::deleteChannel(geChannel *gch) /* -------------------------------------------------------------------------- */ -void geKeyboard::updateChannel(geChannel *gch) +void geKeyboard::updateChannel(geChannel* gch) { gch->update(); } @@ -123,34 +123,32 @@ void geKeyboard::updateChannel(geChannel *gch) void geKeyboard::organizeColumns() { - /* if only one column exists don't cleanup: the initial column must - * stay here. */ - - if (columns.size() == 1) + if (columns.size() == 0) return; - /* otherwise delete all empty columns */ - /** FIXME - this for loop might not work correctly! */ + /* Otherwise delete all empty columns. */ - for (unsigned i=columns.size()-1; i>=1; i--) { + for (size_t i=columns.size(); i-- > 0;) { if (columns.at(i)->isEmpty()) { - //Fl::delete_widget(columns.at(i)); - delete columns.at(i); + Fl::delete_widget(columns.at(i)); columns.erase(columns.begin() + i); } } - /* compact column, avoid empty spaces */ - - for (unsigned i=1; iposition(columns.at(i-1)->x() + columns.at(i-1)->w() + 16, y()); + /* Zero columns? Just add the "add column" button. Compact column and avoid + empty spaces otherwise. */ - addColumnBtn->position(columns.back()->x() + columns.back()->w() + 16, y()); - - /* recompute col indexes */ + if (columns.size() == 0) + addColumnBtn->position(x() - xposition(), y()); + else { + for (size_t i=0; ix() + columns.at(i-1)->w() + COLUMN_GAP; + columns.at(i)->position(pos, y()); + } + addColumnBtn->position(columns.back()->x() + columns.back()->w() + COLUMN_GAP, y()); + } refreshColIndexes(); - redraw(); } @@ -158,7 +156,7 @@ void geKeyboard::organizeColumns() /* -------------------------------------------------------------------------- */ -void geKeyboard::cb_addColumn(Fl_Widget *v, void *p) +void geKeyboard::cb_addColumn(Fl_Widget* v, void* p) { ((geKeyboard*)p)->__cb_addColumn(G_DEFAULT_COLUMN_WIDTH); } @@ -167,9 +165,9 @@ void geKeyboard::cb_addColumn(Fl_Widget *v, void *p) /* -------------------------------------------------------------------------- */ -geChannel *geKeyboard::addChannel(int colIndex, Channel *ch, bool build) +geChannel* geKeyboard::addChannel(int colIndex, Channel* ch, bool build) { - geColumn *col = getColumnByIndex(colIndex); + geColumn* col = getColumnByIndex(colIndex); /* no column with index 'colIndex' found? Just create it and set its index to 'colIndex'. */ @@ -199,7 +197,7 @@ void geKeyboard::refreshColumns() /* -------------------------------------------------------------------------- */ -geColumn *geKeyboard::getColumnByIndex(int index) +geColumn* geKeyboard::getColumnByIndex(int index) { for (unsigned i=0; igetIndex() == index) @@ -271,7 +269,7 @@ int geKeyboard::handle(int e) for (unsigned i=0; ichildren(); k++) - ret &= ((geChannel*)columns.at(i)->child(k))->keyPress(e); + ret &= static_cast(columns.at(i)->child(k))->keyPress(e); break; } } @@ -285,7 +283,7 @@ int geKeyboard::handle(int e) void geKeyboard::clear() { for (unsigned i=0; iposition(8, y()); @@ -295,7 +293,7 @@ void geKeyboard::clear() /* -------------------------------------------------------------------------- */ -void geKeyboard::setChannelWithActions(geSampleChannel *gch) +void geKeyboard::setChannelWithActions(geSampleChannel* gch) { if (gch->ch->hasActions) gch->showActionButton(); @@ -309,19 +307,11 @@ void geKeyboard::setChannelWithActions(geSampleChannel *gch) void geKeyboard::printChannelMessage(int res) { - if (res == SAMPLE_NOT_VALID) - gdAlert("This is not a valid WAVE file."); - else if (res == SAMPLE_MULTICHANNEL) + if (res == G_RES_ERR_WRONG_DATA) gdAlert("Multichannel samples not supported."); - else if (res == SAMPLE_WRONG_BIT) - gdAlert("This sample has an\nunsupported bit-depth (> 32 bit)."); - else if (res == SAMPLE_WRONG_ENDIAN) - gdAlert("This sample has a wrong\nbyte order (not little-endian)."); - else if (res == SAMPLE_WRONG_FORMAT) - gdAlert("This sample is encoded in\nan unsupported audio format."); - else if (res == SAMPLE_READ_ERROR) + else if (res == G_RES_ERR_IO) gdAlert("Unable to read this sample."); - else if (res == SAMPLE_PATH_TOO_LONG) + else if (res == G_RES_ERR_PATH_TOO_LONG) gdAlert("File path too long."); else gdAlert("Unknown error."); @@ -335,27 +325,26 @@ void geKeyboard::__cb_addColumn(int width) { int colx; int colxw; - int gap = 16; if (columns.size() == 0) { colx = x() - xposition(); // mind the offset with xposition() colxw = colx + width; } else { - geColumn *prev = columns.back(); - colx = prev->x()+prev->w() + gap; + geColumn* prev = columns.back(); + colx = prev->x()+prev->w() + COLUMN_GAP; colxw = colx + width; } /* add geColumn to geKeyboard and to columns vector */ - geColumn *gc = new geColumn(colx, y(), width, 2000, indexColumn, this); + geColumn* gc = new geColumn(colx, y(), width, 2000, indexColumn, this); add(gc); columns.push_back(gc); indexColumn++; /* move addColumn button */ - addColumnBtn->position(colxw + gap, y()); + addColumnBtn->position(colxw + COLUMN_GAP, y()); redraw(); gu_log("[geKeyboard::__cb_addColumn] new column added (index=%d, w=%d), total count=%d, addColumn(x)=%d\n", @@ -398,7 +387,7 @@ int geKeyboard::getColumnWidth(int i) /* -------------------------------------------------------------------------- */ -geColumn *geKeyboard::getColumn(int i) +geColumn* geKeyboard::getColumn(int i) { return columns.at(i); } diff --git a/src/gui/elems/mainWindow/keyboard/keyboard.h b/src/gui/elems/mainWindow/keyboard/keyboard.h index a73cdab..89c3b4e 100644 --- a/src/gui/elems/mainWindow/keyboard/keyboard.h +++ b/src/gui/elems/mainWindow/keyboard/keyboard.h @@ -45,13 +45,15 @@ class geKeyboard : public Fl_Scroll { private: + static const int COLUMN_GAP = 16; + /* refreshColIndexes * Recompute all column indexes in order to avoid any gaps between them. * Indexes must always be contiguous! */ void refreshColIndexes(); - static void cb_addColumn (Fl_Widget *v, void *p); + static void cb_addColumn (Fl_Widget* v, void* p); inline void __cb_addColumn(int width=G_DEFAULT_COLUMN_WIDTH); bool bckspcPressed; @@ -64,7 +66,7 @@ private: static int indexColumn; - geButton *addColumnBtn; + geButton* addColumnBtn; /* columns * a vector of columns which in turn contain channels. */ @@ -88,7 +90,7 @@ public: * set to true, also generate the corresponding column if column (index) does * not exist yet. */ - geChannel *addChannel(int column, Channel *ch, bool build=false); + geChannel* addChannel(int column, Channel* ch, bool build=false); /* addColumn * add a new column to the top of the stack. */ @@ -99,18 +101,18 @@ public: * delete a channel from geChannels<> where geChannel->ch == ch and remove * it from the stack. */ - void deleteChannel(geChannel *gch); + void deleteChannel(geChannel* gch); /* freeChannel * free a channel from geChannels<> where geChannel->ch == ch. No channels * are deleted */ - void freeChannel(geChannel *gch); + void freeChannel(geChannel* gch); /* updateChannel * wrapper function to call gch->update(). */ - void updateChannel(geChannel *gch); + void updateChannel(geChannel* gch); /* organizeColumns * reorganize columns layout by removing empty gaps. */ @@ -125,12 +127,12 @@ public: /* getColumnByIndex * return the column with index 'index', or nullptr if not found. */ - geColumn *getColumnByIndex(int index); + geColumn* getColumnByIndex(int index); /* getColumn * return the column with from columns->at(i). */ - geColumn *getColumn(int i); + geColumn* getColumn(int i); /* clear * delete all channels and groups. */ @@ -140,7 +142,7 @@ public: /* setChannelWithActions * add 'R' button if channel has actions, and set recorder to active. */ - void setChannelWithActions(geSampleChannel *gch); + void setChannelWithActions(geSampleChannel* gch); /* printChannelMessage * given any output by glue_loadChannel, print the message on screen diff --git a/src/gui/elems/mainWindow/keyboard/sampleChannel.cpp b/src/gui/elems/mainWindow/keyboard/sampleChannel.cpp index 26f1310..b22dcc1 100644 --- a/src/gui/elems/mainWindow/keyboard/sampleChannel.cpp +++ b/src/gui/elems/mainWindow/keyboard/sampleChannel.cpp @@ -368,7 +368,7 @@ void geSampleChannel::update() mainButton->label("* file not found! *"); break; default: - mainButton->label(((SampleChannel*) ch)->wave->name.c_str()); + mainButton->label(((SampleChannel*) ch)->wave->getName().c_str()); break; } diff --git a/src/gui/elems/mainWindow/keyboard/sampleChannelButton.cpp b/src/gui/elems/mainWindow/keyboard/sampleChannelButton.cpp index 781437a..66f8867 100644 --- a/src/gui/elems/mainWindow/keyboard/sampleChannelButton.cpp +++ b/src/gui/elems/mainWindow/keyboard/sampleChannelButton.cpp @@ -64,7 +64,7 @@ int geSampleChannelButton::handle(int e) geSampleChannel *gch = static_cast(parent()); SampleChannel *ch = static_cast(gch->ch); int result = glue_loadChannel(ch, gu_trim(gu_stripFileUrl(Fl::event_text())).c_str()); - if (result != SAMPLE_LOADED_OK) + if (result != G_RES_OK) G_MainWin->keyboard->printChannelMessage(result); ret = 1; break; diff --git a/src/gui/elems/mainWindow/mainMenu.cpp b/src/gui/elems/mainWindow/mainMenu.cpp index 85974d3..a61f8ed 100644 --- a/src/gui/elems/mainWindow/mainMenu.cpp +++ b/src/gui/elems/mainWindow/mainMenu.cpp @@ -49,7 +49,7 @@ #include "mainMenu.h" -extern gdMainWindow *G_MainWin; +extern gdMainWindow* G_MainWin; using namespace giada::m; @@ -79,10 +79,10 @@ geMainMenu::geMainMenu(int x, int y) /* -------------------------------------------------------------------------- */ -void geMainMenu::cb_about (Fl_Widget *v, void *p) { ((geMainMenu*)p)->__cb_about(); } -void geMainMenu::cb_config(Fl_Widget *v, void *p) { ((geMainMenu*)p)->__cb_config(); } -void geMainMenu::cb_file (Fl_Widget *v, void *p) { ((geMainMenu*)p)->__cb_file(); } -void geMainMenu::cb_edit (Fl_Widget *v, void *p) { ((geMainMenu*)p)->__cb_edit(); } +void geMainMenu::cb_about (Fl_Widget* v, void* p) { ((geMainMenu*)p)->__cb_about(); } +void geMainMenu::cb_config(Fl_Widget* v, void* p) { ((geMainMenu*)p)->__cb_config(); } +void geMainMenu::cb_file (Fl_Widget* v, void* p) { ((geMainMenu*)p)->__cb_file(); } +void geMainMenu::cb_edit (Fl_Widget* v, void* p) { ((geMainMenu*)p)->__cb_edit(); } /* -------------------------------------------------------------------------- */ @@ -118,13 +118,13 @@ void geMainMenu::__cb_file() {0} }; - Fl_Menu_Button *b = new Fl_Menu_Button(0, 0, 100, 50); + Fl_Menu_Button* b = new Fl_Menu_Button(0, 0, 100, 50); b->box(G_CUSTOM_BORDER_BOX); b->textsize(G_GUI_FONT_SIZE_BASE); b->textcolor(G_COLOR_LIGHT_2); b->color(G_COLOR_GREY_2); - const Fl_Menu_Item *m = menu->popup(Fl::event_x(), Fl::event_y(), 0, 0, b); + const Fl_Menu_Item* m = menu->popup(Fl::event_x(), Fl::event_y(), 0, 0, b); if (!m) return; if (strcmp(m->label(), "Open patch or project...") == 0) { @@ -189,13 +189,13 @@ void geMainMenu::__cb_edit() break; } - Fl_Menu_Button *b = new Fl_Menu_Button(0, 0, 100, 50); + Fl_Menu_Button* b = new Fl_Menu_Button(0, 0, 100, 50); b->box(G_CUSTOM_BORDER_BOX); b->textsize(G_GUI_FONT_SIZE_BASE); b->textcolor(G_COLOR_LIGHT_2); b->color(G_COLOR_GREY_2); - const Fl_Menu_Item *m = menu->popup(Fl::event_x(), Fl::event_y(), 0, 0, b); + const Fl_Menu_Item* m = menu->popup(Fl::event_x(), Fl::event_y(), 0, 0, b); if (!m) return; if (strcmp(m->label(), "Clear all samples") == 0) { @@ -215,7 +215,6 @@ void geMainMenu::__cb_edit() if (strcmp(m->label(), "Reset to init state") == 0) { if (!gdConfirmWin("Warning", "Reset to init state: are you sure?")) return; - gu_closeAllSubwindows(); glue_resetToInitState(); return; } diff --git a/src/gui/elems/sampleEditor/boostTool.cpp b/src/gui/elems/sampleEditor/boostTool.cpp index 9a72a4f..e189525 100644 --- a/src/gui/elems/sampleEditor/boostTool.cpp +++ b/src/gui/elems/sampleEditor/boostTool.cpp @@ -41,6 +41,9 @@ #include "boostTool.h" +using namespace giada::m; + + geBoostTool::geBoostTool(int X, int Y, SampleChannel *ch) : Fl_Group(X, Y, 220, 20), ch (ch) @@ -96,7 +99,7 @@ void geBoostTool::__cb_setBoost() else if (Fl::event() == FL_RELEASE) { glue_setBoost(ch, dial->value()); - static_cast(parent()->parent())->waveTools->updateWaveform(); + static_cast(window())->waveTools->updateWaveform(); } } @@ -107,7 +110,7 @@ void geBoostTool::__cb_setBoost() void geBoostTool::__cb_setBoostNum() { glue_setBoost(ch, gu_dBtoLinear(atof(input->value()))); - static_cast(parent()->parent())->waveTools->updateWaveform(); + static_cast(window())->waveTools->updateWaveform(); } @@ -116,8 +119,8 @@ void geBoostTool::__cb_setBoostNum() void geBoostTool::__cb_normalize() { - float val = wfx_normalizeSoft(ch->wave); + float val = wfx::normalizeSoft(ch->wave); glue_setBoost(ch, val); // it's like a fake user moving the dial - static_cast(parent()->parent())->waveTools->updateWaveform(); + static_cast(window())->waveTools->updateWaveform(); } diff --git a/src/gui/elems/sampleEditor/pitchTool.cpp b/src/gui/elems/sampleEditor/pitchTool.cpp index 8e6f439..84da01e 100644 --- a/src/gui/elems/sampleEditor/pitchTool.cpp +++ b/src/gui/elems/sampleEditor/pitchTool.cpp @@ -141,7 +141,7 @@ void gePitchTool::__cb_setPitchDouble() void gePitchTool::__cb_setPitchToBar() { - glue_setPitch(ch, ch->end / (float) clock::getFramesPerBar()); + glue_setPitch(ch, ch->getEnd() / (float) clock::getFramesPerBar()); } @@ -150,7 +150,7 @@ void gePitchTool::__cb_setPitchToBar() void gePitchTool::__cb_setPitchToSong() { - glue_setPitch(ch, ch->end / (float) clock::getTotalFrames()); + glue_setPitch(ch, ch->getEnd() / (float) clock::getTotalFrames()); } diff --git a/src/gui/elems/sampleEditor/rangeTool.cpp b/src/gui/elems/sampleEditor/rangeTool.cpp index 8f411ff..8abd173 100644 --- a/src/gui/elems/sampleEditor/rangeTool.cpp +++ b/src/gui/elems/sampleEditor/rangeTool.cpp @@ -43,26 +43,26 @@ using namespace giada::c; -geRangeTool::geRangeTool(int x, int y, SampleChannel *ch) - : Fl_Group(x, y, 300, 20), - ch (ch) +geRangeTool::geRangeTool(int x, int y, SampleChannel* ch) + : Fl_Group(x, y, 300, 20), + m_ch (ch) { begin(); - label = new geBox (x, y, gu_getStringWidth("Range"), 20, "Range", FL_ALIGN_RIGHT); - begin_ = new geInput(label->x()+label->w()+4, y, 70, 20); - end_ = new geInput(begin_->x()+begin_->w()+4, y, 70, 20); - reset = new geButton(end_->x()+end_->w()+4, y, 70, 20, "Reset"); + m_label = new geBox (x, y, gu_getStringWidth("Range"), 20, "Range", FL_ALIGN_RIGHT); + m_begin = new geInput(m_label->x()+m_label->w()+4, y, 70, 20); + m_end = new geInput(m_begin->x()+m_begin->w()+4, y, 70, 20); + m_reset = new geButton(m_end->x()+m_end->w()+4, y, 70, 20, "Reset"); end(); - begin_->type(FL_INT_INPUT); - begin_->when(FL_WHEN_RELEASE | FL_WHEN_ENTER_KEY); // on focus lost or enter key - begin_->callback(cb_setChanPos, this); + m_begin->type(FL_INT_INPUT); + m_begin->when(FL_WHEN_RELEASE | FL_WHEN_ENTER_KEY); // on focus lost or enter key + m_begin->callback(cb_setChanPos, this); - end_->type(FL_INT_INPUT); - end_->when(FL_WHEN_RELEASE | FL_WHEN_ENTER_KEY); // on focus lost or enter key - end_->callback(cb_setChanPos, this); + m_end->type(FL_INT_INPUT); + m_end->when(FL_WHEN_RELEASE | FL_WHEN_ENTER_KEY); // on focus lost or enter key + m_end->callback(cb_setChanPos, this); - reset->callback(cb_resetStartEnd, this); + m_reset->callback(cb_resetStartEnd, this); refresh(); } @@ -73,16 +73,16 @@ geRangeTool::geRangeTool(int x, int y, SampleChannel *ch) void geRangeTool::refresh() { - begin_->value(gu_itoa(ch->begin / 2).c_str()); - end_->value(gu_itoa(ch->end / 2).c_str()); + m_begin->value(gu_toString(m_ch->getBegin()).c_str()); + m_end->value(gu_toString(m_ch->getEnd()).c_str()); } /* -------------------------------------------------------------------------- */ -void geRangeTool::cb_setChanPos (Fl_Widget *w, void *p) { ((geRangeTool*)p)->__cb_setChanPos(); } -void geRangeTool::cb_resetStartEnd(Fl_Widget *w, void *p) { ((geRangeTool*)p)->__cb_resetStartEnd(); } +void geRangeTool::cb_setChanPos (Fl_Widget* w, void* p) { ((geRangeTool*)p)->__cb_setChanPos(); } +void geRangeTool::cb_resetStartEnd(Fl_Widget* w, void* p) { ((geRangeTool*)p)->__cb_resetStartEnd(); } /* -------------------------------------------------------------------------- */ @@ -90,8 +90,8 @@ void geRangeTool::cb_resetStartEnd(Fl_Widget *w, void *p) { ((geRangeTool*)p)->_ void geRangeTool::__cb_setChanPos() { - sampleEditor::setBeginEndChannel(ch, atoi(begin_->value())*2, atoi(end_->value())*2); - static_cast(parent()->parent())->waveTools->updateWaveform(); + sampleEditor::setBeginEndChannel(m_ch, atoi(m_begin->value()), atoi(m_end->value())); + static_cast(window())->waveTools->updateWaveform(); } @@ -100,6 +100,6 @@ void geRangeTool::__cb_setChanPos() void geRangeTool::__cb_resetStartEnd() { - sampleEditor::setBeginEndChannel(ch, 0, ch->wave->size); - static_cast(parent()->parent())->waveTools->updateWaveform(); + sampleEditor::setBeginEndChannel(m_ch, 0, m_ch->wave->getSize() - 1); + static_cast(window())->waveTools->updateWaveform(); } diff --git a/src/gui/elems/sampleEditor/rangeTool.h b/src/gui/elems/sampleEditor/rangeTool.h index 909f0f3..0480388 100644 --- a/src/gui/elems/sampleEditor/rangeTool.h +++ b/src/gui/elems/sampleEditor/rangeTool.h @@ -42,21 +42,21 @@ class geRangeTool : public Fl_Group { private: - SampleChannel *ch; + SampleChannel* m_ch; - geBox *label; - geInput *begin_; - geInput *end_; - geButton *reset; + geBox* m_label; + geInput* m_begin; + geInput* m_end; + geButton* m_reset; - static void cb_setChanPos (Fl_Widget *w, void *p); - static void cb_resetStartEnd(Fl_Widget *w, void *p); + static void cb_setChanPos (Fl_Widget* w, void* p); + static void cb_resetStartEnd(Fl_Widget* w, void* p); inline void __cb_setChanPos(); inline void __cb_resetStartEnd(); public: - geRangeTool(int x, int y, SampleChannel *ch); + geRangeTool(int x, int y, SampleChannel* ch); void refresh(); }; diff --git a/src/gui/elems/sampleEditor/waveTools.cpp b/src/gui/elems/sampleEditor/waveTools.cpp index e3c0c13..0b7e059 100644 --- a/src/gui/elems/sampleEditor/waveTools.cpp +++ b/src/gui/elems/sampleEditor/waveTools.cpp @@ -80,10 +80,10 @@ void menuCallback(Fl_Widget* w, void* v) c::sampleEditor::silence(wavetools->ch, a, b); break; case Menu::FADE_IN: - c::sampleEditor::fade(wavetools->ch, a, b, 0); + c::sampleEditor::fade(wavetools->ch, a, b, m::wfx::FADE_IN); break; case Menu::FADE_OUT: - c::sampleEditor::fade(wavetools->ch, a, b, 1); + c::sampleEditor::fade(wavetools->ch, a, b, m::wfx::FADE_OUT); break; case Menu::SMOOTH_EDGES: c::sampleEditor::smoothEdges(wavetools->ch, a, b); @@ -128,7 +128,7 @@ void geWaveTools::updateWaveform() void geWaveTools::redrawWaveformAsync() { - if (ch->status & (STATUS_PLAY | STATUS_ENDING)) + if (ch->isPreview()) waveform->redraw(); } @@ -161,23 +161,22 @@ void geWaveTools::resize(int x, int y, int w, int h) int geWaveTools::handle(int e) { - int ret = Fl_Group::handle(e); switch (e) { case FL_MOUSEWHEEL: { waveform->setZoom(Fl::event_dy()); redraw(); - ret = 1; - break; + return 1; } case FL_PUSH: { if (Fl::event_button3()) { // right button openMenu(); - ret = 1; + return 1; } - break; + Fl::focus(waveform); } + default: + return Fl_Group::handle(e); } - return ret; } diff --git a/src/gui/elems/sampleEditor/waveform.cpp b/src/gui/elems/sampleEditor/waveform.cpp index e315ca1..d153ad1 100644 --- a/src/gui/elems/sampleEditor/waveform.cpp +++ b/src/gui/elems/sampleEditor/waveform.cpp @@ -37,6 +37,8 @@ #include "../../../core/sampleChannel.h" #include "../../../glue/channel.h" #include "../../../glue/sampleEditor.h" +#include "../../../utils/log.h" +#include "../../dialogs/sampleEditor.h" #include "../basics/boxtypes.h" #include "waveTools.h" #include "waveform.h" @@ -47,25 +49,25 @@ using namespace giada::c; geWaveform::geWaveform(int x, int y, int w, int h, SampleChannel* ch, const char* l) -: Fl_Widget (x, y, w, h, l), - selection {}, - chan (ch), - chanStart (0), - chanStartLit(false), - chanEnd (0), - chanEndLit (false), - pushed (false), - dragged (false), - resizedA (false), - resizedB (false), - ratio (0.0f) +: Fl_Widget (x, y, w, h, l), + m_selection {}, + m_ch (ch), + m_chanStart (0), + m_chanStartLit(false), + m_chanEnd (0), + m_chanEndLit (false), + m_pushed (false), + m_dragged (false), + m_resizedA (false), + m_resizedB (false), + m_ratio (0.0f) { - data.sup = nullptr; - data.inf = nullptr; - data.size = 0; + m_data.sup = nullptr; + m_data.inf = nullptr; + m_data.size = 0; - grid.snap = conf::sampleEditorGridOn; - grid.level = conf::sampleEditorGridVal; + m_grid.snap = conf::sampleEditorGridOn; + m_grid.level = conf::sampleEditorGridVal; alloc(w); } @@ -85,98 +87,101 @@ geWaveform::~geWaveform() void geWaveform::freeData() { - if (data.sup) { - delete[] data.sup; - delete[] data.inf; - data.sup = nullptr; - data.inf = nullptr; - data.size = 0; + if (m_data.sup) { + delete[] m_data.sup; + delete[] m_data.inf; + m_data.sup = nullptr; + m_data.inf = nullptr; + m_data.size = 0; } - grid.points.clear(); + m_grid.points.clear(); } /* -------------------------------------------------------------------------- */ -int geWaveform::alloc(int datasize) +int geWaveform::alloc(int datasize, bool force) { - ratio = chan->wave->size / (float) datasize; + Wave* wave = m_ch->wave; - if (ratio < 2) - return 0; + m_ratio = wave->getSize() / (float) datasize; + + /* Limit 1:1 drawing (to avoid sub-frame drawing) by keeping m_ratio >= 1. */ + + if (m_ratio < 1) { + datasize = wave->getSize(); + m_ratio = 1; + } + + if (datasize == m_data.size && !force) + return 0; freeData(); - data.size = datasize; - data.sup = new (std::nothrow) int[data.size]; - data.inf = new (std::nothrow) int[data.size]; + m_data.size = datasize; + m_data.sup = new (std::nothrow) int[m_data.size]; + m_data.inf = new (std::nothrow) int[m_data.size]; - if (!data.sup || !data.inf) + if (!m_data.sup || !m_data.inf) { + gu_log("[geWaveform::alloc] unable to allocate memory for the waveform!\n"); return 0; + } + + gu_log("[geWaveform::alloc] %d pixels, %f m_ratio\n", m_data.size, m_ratio); int offset = h() / 2; int zero = y() + offset; // center, zero amplitude (-inf dB) - /* grid frequency: store a grid point every 'gridFreq' pixel. Must be - * even, as always */ - - int gridFreq = 0; - if (grid.level != 0) { - gridFreq = chan->wave->size / grid.level; - if (gridFreq % 2 != 0) - gridFreq--; - } + /* Frid frequency: store a grid point every 'gridFreq' frame (if grid is + enabled). TODO - this will cause round off errors, since gridFreq is integer. */ - for (int i=0; igetSize() / m_grid.level : 0; - int pp; // point prev - int pn; // point next + /* Resampling the waveform, hardcore way. Many thanks to + http://fourier.eng.hmc.edu/e161/lectures/resize/node3.html */ - /* resampling the waveform, hardcore way. Many thanks to - * http://fourier.eng.hmc.edu/e161/lectures/resize/node3.html - * Note: we use - * p = j * (m-1 / n) - * instead of - * p = j * (m-1 / n-1) - * in order to obtain 'datasize' cells to parse (and not datasize-1) */ + for (int i=0; iwave->size - 1) / (float) datasize); - pn = (i+1) * ((chan->wave->size - 1) / (float) datasize); - - if (pp % 2 != 0) pp -= 1; - if (pn % 2 != 0) pn -= 1; + float pc = i * m_ratio; // current point + float pn = (i+1) * m_ratio; // next point float peaksup = 0.0f; float peakinf = 0.0f; - /* scan the original data in chunks */ + for (float k=pc; k= wave->getSize()) + continue; - if (chan->wave->data[k] > peaksup) - peaksup = chan->wave->data[k]; // FIXME - Left data only - else - if (chan->wave->data[k] <= peakinf) - peakinf = chan->wave->data[k]; // FIXME - Left data only + /* Compute average of stereo signal. */ - /* print grid */ + float avg = 0.0f; + float* frame = wave->getFrame(k); + for (int j=0; jgetChannels(); j++) + avg += frame[j]; + avg /= wave->getChannels(); + + /* Find peaks (greater and lower). */ - if (gridFreq != 0) - if (k % gridFreq == 0 && k != 0) - grid.points.push_back(i); + if (avg > peaksup) peaksup = avg; + else if (avg <= peakinf) peakinf = avg; - k += 2; + /* Fill up grid vector. */ + + if (gridFreq != 0 && (int) k % gridFreq == 0 && k != 0) + m_grid.points.push_back(k); } - data.sup[i] = zero - (peaksup * chan->getBoost() * offset); - data.inf[i] = zero - (peakinf * chan->getBoost() * offset); + m_data.sup[i] = zero - (peaksup * m_ch->getBoost() * offset); + m_data.inf[i] = zero - (peakinf * m_ch->getBoost() * offset); // avoid window overflow - if (data.sup[i] < y()) data.sup[i] = y(); - if (data.inf[i] > y()+h()-1) data.inf[i] = y()+h()-1; + if (m_data.sup[i] < y()) m_data.sup[i] = y(); + if (m_data.inf[i] > y()+h()-1) m_data.inf[i] = y()+h()-1; } recalcPoints(); @@ -189,10 +194,8 @@ int geWaveform::alloc(int datasize) void geWaveform::recalcPoints() { - selection.aPixel = relativePoint(selection.aFrame); - selection.bPixel = relativePoint(selection.bFrame); - chanStart = relativePoint(chan->begin / 2); - chanEnd = relativePoint(chan->end / 2); + m_chanStart = m_ch->getBegin(); + m_chanEnd = m_ch->getEnd(); } @@ -204,18 +207,18 @@ void geWaveform::drawSelection() if (!isSelected()) return; - int a_x = selection.aPixel + x(); // - start; - int b_x = selection.bPixel + x(); // - start; + int a = frameToPixel(m_selection.a) + x(); + int b = frameToPixel(m_selection.b) + x(); - if (a_x < 0) - a_x = 0; - if (b_x >= w() + BORDER) - b_x = w() + BORDER; + if (a < 0) + a = 0; + if (b >= w() + BORDER) + b = w() + BORDER; - if (selection.aPixel < selection.bPixel) - fl_rectf(a_x, y(), b_x-a_x, h(), G_COLOR_GREY_4); + if (a < b) + fl_rectf(a, y(), b-a, h(), G_COLOR_GREY_4); else - fl_rectf(b_x, y(), a_x-b_x, h(), G_COLOR_GREY_4); + fl_rectf(b, y(), a-b, h(), G_COLOR_GREY_4); } @@ -228,8 +231,10 @@ void geWaveform::drawWaveform(int from, int to) fl_color(G_COLOR_BLACK); for (int i=from; i= m_data.size) + break; + fl_line(i+x(), zero, i+x(), m_data.sup[i]); + fl_line(i+x(), zero, i+x(), m_data.inf[i]); } } @@ -239,17 +244,16 @@ void geWaveform::drawWaveform(int from, int to) void geWaveform::drawGrid(int from, int to) { - fl_color(G_COLOR_GREY_3); - fl_line_style(FL_DASH, 1, nullptr); - for (int i=from; i from && pp < to) + fl_line(pp+x(), y(), pp+x(), y()+h()); + } + + fl_line_style(FL_SOLID, 0, nullptr); } @@ -258,11 +262,11 @@ void geWaveform::drawGrid(int from, int to) void geWaveform::drawStartEndPoints() { - /* print chanStart */ + /* print m_chanStart */ - int lineX = chanStart + x(); + int lineX = frameToPixel(m_chanStart) + x(); - if (chanStartLit) fl_color(G_COLOR_LIGHT_2); + if (m_chanStartLit) fl_color(G_COLOR_LIGHT_2); else fl_color(G_COLOR_LIGHT_1); /* vertical line */ @@ -276,10 +280,10 @@ void geWaveform::drawStartEndPoints() else fl_rectf(lineX, y()+h()-FLAG_HEIGHT-1, FLAG_WIDTH, FLAG_HEIGHT); - /* print chanEnd */ + /* print m_chanEnd */ - lineX = chanEnd + x() - 1; - if (chanEndLit) fl_color(G_COLOR_LIGHT_2); + lineX = frameToPixel(m_chanEnd) + x() - 1; + if (m_chanEndLit) fl_color(G_COLOR_LIGHT_2); else fl_color(G_COLOR_LIGHT_1); /* vertical line */ @@ -298,9 +302,7 @@ void geWaveform::drawStartEndPoints() void geWaveform::drawPlayHead() { - if (chan->status == STATUS_OFF) - return; - int p = ceilf(chan->tracker / ratio) + x(); + int p = frameToPixel(m_ch->getTrackerPreview()) + x(); fl_color(G_COLOR_LIGHT_2); fl_line(p, y() + 1, p, y() + h() - 2); } @@ -311,8 +313,8 @@ void geWaveform::drawPlayHead() void geWaveform::draw() { - assert(data.sup != nullptr); - assert(data.inf != nullptr); + assert(m_data.sup != nullptr); + assert(m_data.inf != nullptr); fl_rectf(x(), y(), w(), h(), G_COLOR_GREY_2); // blank canvas @@ -340,102 +342,97 @@ void geWaveform::draw() int geWaveform::handle(int e) { - int ret = 0; + m_mouseX = pixelToFrame(Fl::event_x() - x()); + m_mouseY = pixelToFrame(Fl::event_y() - y()); switch (e) { + case FL_KEYDOWN: { + if (Fl::event_key() == ' ') + static_cast(window())->__cb_togglePreview(); + else + if (Fl::event_key() == FL_BackSpace) + sampleEditor::rewindPreview(m_ch); + return 1; + } + case FL_PUSH: { - mouseX = Fl::event_x(); - pushed = true; + m_pushed = true; if (!mouseOnEnd() && !mouseOnStart()) { - if (Fl::event_button3()) { // let the parent (waveTools) handle this - ret = 0; - break; - } + if (Fl::event_button3()) // let the parent (waveTools) handle this + return 0; if (mouseOnSelectionA()) - resizedA = true; + m_resizedA = true; else if(mouseOnSelectionB()) - resizedB = true; + m_resizedB = true; else { - dragged = true; - selection.aPixel = Fl::event_x() - x(); - selection.bPixel = selection.aPixel; + m_dragged = true; + m_selection.a = m_mouseX; + m_selection.b = m_mouseX; } } - ret = 1; - break; + return 1; } case FL_RELEASE: { - /* If selection has been done (dragged or resized), make sure that point A + sampleEditor::setPlayHead(m_ch, m_mouseX); + + /* If selection has been done (m_dragged or resized), make sure that point A is always lower than B. */ - if (dragged || resizedA || resizedB) + if (m_dragged || m_resizedA || m_resizedB) fixSelection(); /* Handle begin/end markers interaction. */ - if (chanStartLit || chanEndLit) { - int realChanStart = chan->begin; - int realChanEnd = chan->end; - if (chanStartLit) - realChanStart = absolutePoint(chanStart) * 2; - else - if (chanEndLit) - realChanEnd = absolutePoint(chanEnd) * 2; - sampleEditor::setBeginEndChannel(chan, realChanStart, realChanEnd); - } + if (m_chanStartLit || m_chanEndLit) + sampleEditor::setBeginEndChannel(m_ch, m_chanStart, m_chanEnd); - pushed = false; - dragged = false; - resizedA = false; - resizedB = false; + m_pushed = false; + m_dragged = false; + m_resizedA = false; + m_resizedB = false; redraw(); - ret = 1; - break; + return 1; } case FL_ENTER: { // enables FL_DRAG - ret = 1; - break; + return 1; } case FL_LEAVE: { - if (chanStartLit || chanEndLit) { - chanStartLit = false; - chanEndLit = false; + if (m_chanStartLit || m_chanEndLit) { + m_chanStartLit = false; + m_chanEndLit = false; redraw(); } - ret = 1; - break; + return 1; } case FL_MOVE: { - mouseX = Fl::event_x(); - mouseY = Fl::event_y(); if (mouseOnStart()) { - chanStartLit = true; + m_chanStartLit = true; redraw(); } else - if (chanStartLit) { - chanStartLit = false; + if (m_chanStartLit) { + m_chanStartLit = false; redraw(); } if (mouseOnEnd()) { - chanEndLit = true; + m_chanEndLit = true; redraw(); } else - if (chanEndLit) { - chanEndLit = false; + if (m_chanEndLit) { + m_chanEndLit = false; redraw(); } @@ -447,42 +444,35 @@ int geWaveform::handle(int e) else fl_cursor(FL_CURSOR_DEFAULT, FL_WHITE, FL_BLACK); - ret = 1; - break; + return 1; } case FL_DRAG: { - /* here the mouse is on the chanStart tool */ + /* here the mouse is on the m_chanStart tool */ - if (chanStartLit && pushed) { + if (m_chanStartLit && m_pushed) { - chanStart = Fl::event_x() - x(); + m_chanStart = snap(m_mouseX); - if (grid.snap) - chanStart = applySnap(chanStart); - - if (chanStart < 0) - chanStart = 0; + if (m_chanStart < 0) + m_chanStart = 0; else - if (chanStart >= chanEnd) - chanStart = chanEnd - 2; + if (m_chanStart >= m_chanEnd) + m_chanStart = m_chanEnd - 2; redraw(); } else - if (chanEndLit && pushed) { - - chanEnd = Fl::event_x() - x(); + if (m_chanEndLit && m_pushed) { - if (grid.snap) - chanEnd = applySnap(chanEnd); + m_chanEnd = snap(m_mouseX); - if (chanEnd > data.size) - chanEnd = data.size; + if (m_chanEnd > m_ch->wave->getSize()) + m_chanEnd = m_ch->wave->getSize(); else - if (chanEnd <= chanStart) - chanEnd = chanStart + 2; + if (m_chanEnd <= m_chanStart) + m_chanEnd = m_chanStart + 2; redraw(); } @@ -490,49 +480,44 @@ int geWaveform::handle(int e) /* Here the mouse is on the waveform, i.e. a new selection has started. */ else - if (dragged) { - selection.bPixel = Fl::event_x() - x(); - if (grid.snap) - selection.bPixel = applySnap(selection.bPixel); + if (m_dragged) { + m_selection.b = snap(m_mouseX); redraw(); } /* here the mouse is on a selection boundary i.e. resize */ else - if (resizedA || resizedB) { - int pos = Fl::event_x() - x(); - if (grid.snap) - pos = applySnap(pos); - resizedA ? selection.aPixel = pos : selection.bPixel = pos; + if (m_resizedA || m_resizedB) { + int pos = snap(m_mouseX); + m_resizedA ? m_selection.a = pos : m_selection.b = pos; redraw(); } - mouseX = Fl::event_x(); - ret = 1; - break; + return 1; } + + default: + return Fl_Widget::handle(e); } - return ret; } /* -------------------------------------------------------------------------- */ -int geWaveform::applySnap(int pos) +int geWaveform::snap(int pos) { - /* Pixel snap disances (SNAPPING) must be equal to those defined in - mouseOnSelectionA() and mouseOnSelectionB(). */ - - for (unsigned i=0; i= grid.points.at(i) - SNAPPING && - pos <= grid.points.at(i) + SNAPPING) - { - return grid.points.at(i); - } - } - return pos; + if (!m_grid.snap) + return pos; + for (int pf : m_grid.points) { + if (pos >= pf - pixelToFrame(SNAPPING) && + pos <= pf + pixelToFrame(SNAPPING)) + { + return pf; + } + } + return pos; } @@ -541,9 +526,12 @@ int geWaveform::applySnap(int pos) bool geWaveform::mouseOnStart() { - return mouseX - (FLAG_WIDTH / 2) > chanStart + x() - BORDER && - mouseX - (FLAG_WIDTH / 2) <= chanStart + x() - BORDER + FLAG_WIDTH && - mouseY > h() + y() - FLAG_HEIGHT; + int mouseXp = frameToPixel(m_mouseX); + int mouseYp = frameToPixel(m_mouseY); + int chanStartP = frameToPixel(m_chanStart); + return mouseXp - (FLAG_WIDTH / 2) > chanStartP - BORDER && + mouseXp - (FLAG_WIDTH / 2) <= chanStartP - BORDER + FLAG_WIDTH && + mouseYp > h() - FLAG_HEIGHT; } @@ -552,52 +540,53 @@ bool geWaveform::mouseOnStart() bool geWaveform::mouseOnEnd() { - return mouseX - (FLAG_WIDTH / 2) >= chanEnd + x() - BORDER - FLAG_WIDTH && - mouseX - (FLAG_WIDTH / 2) <= chanEnd + x() - BORDER && - mouseY <= y() + FLAG_HEIGHT + 1; + int mouseXp = frameToPixel(m_mouseX); + int mouseYp = frameToPixel(m_mouseY); + int chanEndP = frameToPixel(m_chanEnd); + return mouseXp - (FLAG_WIDTH / 2) >= chanEndP - BORDER - FLAG_WIDTH && + mouseXp - (FLAG_WIDTH / 2) <= chanEndP - BORDER && + mouseYp <= FLAG_HEIGHT + 1; } /* -------------------------------------------------------------------------- */ -/* pixel boundaries (10px) must be equal to the snap factor distance - * defined in geWaveform::applySnap() */ bool geWaveform::mouseOnSelectionA() { - return mouseX >= selection.aPixel - (FLAG_WIDTH / 2) + x() && - mouseX <= selection.aPixel + (FLAG_WIDTH / 2) + x(); + int mouseXp = frameToPixel(m_mouseX); + int selAp = frameToPixel(m_selection.a); + return mouseXp >= selAp - (FLAG_WIDTH / 2) && mouseXp <= selAp + (FLAG_WIDTH / 2); } bool geWaveform::mouseOnSelectionB() { - return mouseX >= selection.bPixel - (FLAG_WIDTH / 2) + x() && - mouseX <= selection.bPixel + (FLAG_WIDTH / 2) + x(); + int mouseXp = frameToPixel(m_mouseX); + int selBp = frameToPixel(m_selection.b); + return mouseXp >= selBp - (FLAG_WIDTH / 2) && mouseXp <= selBp + (FLAG_WIDTH / 2); } /* -------------------------------------------------------------------------- */ -int geWaveform::absolutePoint(int p) +int geWaveform::pixelToFrame(int p) { if (p <= 0) return 0; - - if (p > data.size) - return chan->wave->size / 2; - - return (p * ratio) / 2; + if (p > m_data.size) + return m_ch->wave->getSize() - 1; + return p * m_ratio; } /* -------------------------------------------------------------------------- */ -int geWaveform::relativePoint(int p) +int geWaveform::frameToPixel(int p) { - return (ceil(p / ratio)) * 2; + return ceil(p / m_ratio); } @@ -606,10 +595,9 @@ int geWaveform::relativePoint(int p) void geWaveform::fixSelection() { - if (selection.aPixel > selection.bPixel) // inverted selection - std::swap(selection.aPixel, selection.bPixel); - selection.aFrame = absolutePoint(selection.aPixel); - selection.bFrame = absolutePoint(selection.bPixel); + if (m_selection.a > m_selection.b) // inverted m_selection + std::swap(m_selection.a, m_selection.b); + sampleEditor::setPlayHead(m_ch, m_selection.a); } @@ -618,10 +606,8 @@ void geWaveform::fixSelection() void geWaveform::clearSel() { - selection.aPixel = 0; - selection.bPixel = 0; - selection.aFrame = 0; - selection.bFrame = 0; + m_selection.a = 0; + m_selection.b = 0; } @@ -630,40 +616,30 @@ void geWaveform::clearSel() void geWaveform::setZoom(int type) { - int newSize = type == ZOOM_IN ? data.size * 2 : data.size / 2; - - if (!alloc(newSize)) + if (!alloc(type == ZOOM_IN ? m_data.size * 2 : m_data.size / 2)) return; - size(newSize, h()); + size(m_data.size, h()); - /* zoom to pointer */ + /* Zoom to cursor. */ + + int newX = -frameToPixel(m_mouseX) + Fl::event_x(); + if (newX > BORDER) + newX = BORDER; + position(newX, y()); - int shift; - if (x() > 0) - shift = Fl::event_x() - x(); - else - if (type == ZOOM_IN) - shift = Fl::event_x() + abs(x()); - else - shift = (Fl::event_x() + abs(x())) / -2; - - if (x() - shift > BORDER) - shift = 0; + /* Avoid overflow when zooming out with scrollbar like that: - position(x() - shift, y()); + |----------[scrollbar]| + Offset vs smaller: + + |[wave------------| offset > 0 smaller = false + |[wave----] | offset < 0, smaller = true + |-------------] | offset < 0, smaller = false */ - /* avoid overflow when zooming out with scrollbar like that: - * |----------[scrollbar]| - * - * offset vs smaller: - * |[wave------------| offset > 0 smaller = false - * |[wave----] | offset < 0, smaller = true - * |-------------] | offset < 0, smaller = false */ - - int parentW = parent()->w(); - int thisW = x() + w() - BORDER; // visible width, not full width + int parentW = parent()->w(); + int thisW = x() + w() - BORDER; // visible width, not full width if (thisW < parentW) position(x() + parentW - thisW, y()); @@ -691,7 +667,7 @@ void geWaveform::stretchToWindow() void geWaveform::refresh() { - alloc(data.size); + alloc(m_data.size, true); // force redraw(); } @@ -710,9 +686,9 @@ bool geWaveform::smaller() void geWaveform::setGridLevel(int l) { - grid.points.clear(); - grid.level = l; - alloc(data.size); + m_grid.points.clear(); + m_grid.level = l; + alloc(m_data.size, true); // force alloc redraw(); } @@ -722,21 +698,29 @@ void geWaveform::setGridLevel(int l) bool geWaveform::isSelected() { - return selection.aPixel != selection.bPixel; + return m_selection.a != m_selection.b; } /* -------------------------------------------------------------------------- */ +void geWaveform::setSnap(bool v) { m_grid.snap = v; } +bool geWaveform::getSnap() { return m_grid.snap; } +int geWaveform::getSize() { return m_data.size; } + + +/* -------------------------------------------------------------------------- */ + + int geWaveform::getSelectionA() { - return selection.aFrame; + return m_selection.a; } int geWaveform::getSelectionB() { - return selection.bFrame; + return m_selection.b; } diff --git a/src/gui/elems/sampleEditor/waveform.h b/src/gui/elems/sampleEditor/waveform.h index 8f7cc8f..a427860 100644 --- a/src/gui/elems/sampleEditor/waveform.h +++ b/src/gui/elems/sampleEditor/waveform.h @@ -43,18 +43,16 @@ private: static const int FLAG_WIDTH = 20; static const int FLAG_HEIGHT = 20; static const int BORDER = 8; // window border <-> widget border - static const int SNAPPING = 10; + static const int SNAPPING = 16; /* selection - Portion of the selected wave, in pixel and in frames. */ + Portion of the selected wave, in frames. */ struct { - int aPixel; - int bPixel; - int aFrame; - int bFrame; - } selection; + int a; + int b; + } m_selection; /* data Real graphic stuff from the underlying waveform. */ @@ -62,18 +60,29 @@ private: struct { int* sup; // upper part of the waveform - int* inf; // lower "" "" "" "" + int* inf; // lower part of the waveform int size; // width of the waveform to draw (in pixel) - } data; + } m_data; struct { bool snap; int level; std::vector points; - } grid; - - SampleChannel* chan; + } m_grid; + + SampleChannel* m_ch; + int m_chanStart; + bool m_chanStartLit; + int m_chanEnd; + bool m_chanEndLit; + bool m_pushed; + bool m_dragged; + bool m_resizedA; + bool m_resizedB; + float m_ratio; + int m_mouseX; + int m_mouseY; /* mouseOnStart/end Is mouse on start or end flag? */ @@ -87,16 +96,8 @@ private: bool mouseOnSelectionA(); bool mouseOnSelectionB(); - /* absolutePoint - From a relative 'p' point (zoom affected) returns the same point zoom 1:1 - based. */ - - int absolutePoint(int p); - - /* relativePoint - From an absolute 'p' point (1:1 zoom) returns the same point zoom affected. */ - - int relativePoint(int p); + int pixelToFrame(int p); + int frameToPixel(int f); /* fixSelection Helper function which flattens the selection if it was made from right to left @@ -115,10 +116,10 @@ private: bool smaller(); - /* applySnap - Snap a point at 'pos' pixel. */ + /* snap + Snaps a point at 'pos' pixel. */ - int applySnap(int pos); + int snap(int pos); /* draw* Drawing functions. */ @@ -141,12 +142,13 @@ public: int handle(int e) override; /* alloc - * allocate memory for the picture */ + Allocates memory for the picture. It's smart enough not to reallocate if + datasize hasn't changed, but it can be forced otherwise. */ - int alloc(int datasize=0); + int alloc(int datasize, bool force=false); /* recalcPoints - * re-calc chanStart, chanEnd, ... */ + * re-calc m_chanStart, m_chanEnd, ... */ void recalcPoints(); @@ -170,10 +172,9 @@ public: void setGridLevel(int l); - void setSnap(bool v) { grid.snap = v; } - bool getSnap() { return grid.snap; } - - int getSize() { return data.size; } + void setSnap(bool v); + bool getSnap(); + int getSize(); /* isSelected Tells whether a portion of the waveform has been selected. */ @@ -187,18 +188,6 @@ public: Removes any active selection. */ void clearSel(); - - int chanStart; - bool chanStartLit; - int chanEnd; - bool chanEndLit; - bool pushed; - bool dragged; - bool resizedA; - bool resizedB; - float ratio; - int mouseX; - int mouseY; }; diff --git a/src/utils/gui.cpp b/src/utils/gui.cpp index b7ea894..9752b12 100644 --- a/src/utils/gui.cpp +++ b/src/utils/gui.cpp @@ -87,7 +87,7 @@ void gu_refreshUI() /* If Sample Editor is open, repaint it (for dynamic play head). */ gdSampleEditor* se = static_cast(gu_getSubwindow(G_MainWin, WID_SAMPLE_EDITOR)); - if (se) + if (se != nullptr) se->waveTools->redrawWaveformAsync(); /* redraw GUI */ diff --git a/src/utils/string.cpp b/src/utils/string.cpp index 0e5cc9c..92df941 100644 --- a/src/utils/string.cpp +++ b/src/utils/string.cpp @@ -29,6 +29,7 @@ #include #include +#include "../core/const.h" #include "string.h" @@ -40,7 +41,7 @@ string gu_getRealPath(const string &path) { string out = ""; -#if defined(__linux__) || defined(__APPLE__) +#if defined(G_OS_LINUX) || defined(G_OS_MAC) char *buf = realpath(path.c_str(), nullptr); @@ -61,12 +62,23 @@ string gu_getRealPath(const string &path) /* -------------------------------------------------------------------------- */ -string gu_itoa(int i) +string gu_toString(int i) { - // TODO - use std::to_string -> http://stackoverflow.com/questions/191757/how-to-concatenate-a-stdstring-and-an-int?rq=1 + /* std::to_string is the way to go. Unfortunately it seems that it isn't + available in gcc's standard library (libstdc++), it is however, available in + libc++ which comes with LLVM/clang. */ + +#ifdef G_OS_MAC + std::stringstream out; out << i; return out.str(); + +#else + + return std::to_string(i); + +#endif } @@ -105,8 +117,8 @@ void gu_split(string in, string sep, vector *v) size_t curr = 0; size_t next = -1; do { - curr = next + 1; - next = full.find_first_of(sep, curr); + curr = next + 1; + next = full.find_first_of(sep, curr); token = full.substr(curr, next - curr); if (token != "") v->push_back(token); diff --git a/src/utils/string.h b/src/utils/string.h index a460a35..401b773 100644 --- a/src/utils/string.h +++ b/src/utils/string.h @@ -42,8 +42,7 @@ std::string gu_replace(std::string in, const std::string &search, std::string gu_trim(const std::string &s); -// TODO - use std::to_string -> http://stackoverflow.com/questions/191757/how-to-concatenate-a-stdstring-and-an-int?rq=1 -std::string gu_itoa(int i); +std::string gu_toString(int i); void gu_split(std::string in, std::string sep, std::vector *v); diff --git a/tests/utils.cpp b/tests/utils.cpp index 378745c..852b77a 100644 --- a/tests/utils.cpp +++ b/tests/utils.cpp @@ -25,7 +25,7 @@ TEST_CASE("Test string utils") { REQUIRE(gu_replace("Giada is cool", "cool", "hot") == "Giada is hot"); REQUIRE(gu_trim(" Giada is cool ") == "Giada is cool"); - REQUIRE(gu_itoa(666) == "666"); + REQUIRE(gu_toString(666) == "666"); vector v; gu_split("Giada is cool", " ", &v); diff --git a/tests/wave.cpp b/tests/wave.cpp index 1466679..4f2e18a 100644 --- a/tests/wave.cpp +++ b/tests/wave.cpp @@ -1,3 +1,4 @@ +#include #include "../src/core/wave.h" #include "catch/single_include/catch.hpp" @@ -5,41 +6,49 @@ using std::string; -#define G_SAMPLE_RATE 44100 -#define G_BUFFER_SIZE 4096 - - TEST_CASE("Test Wave class") { - Wave w1; + static const int SAMPLE_RATE = 44100; + static const int BUFFER_SIZE = 4096; + static const int CHANNELS = 2; + static const int BIT_DEPTH = 32; - SECTION("test read & write") + /* Each SECTION the TEST_CASE is executed from the start. Any code between + this comment and the first SECTION macro is exectuted before each SECTION. */ + + std::unique_ptr wave; + + SECTION("test basename") { - REQUIRE(w1.open("tests/resources/test.wav") == 1); - REQUIRE(w1.readData() == 1); - REQUIRE(w1.rate() == 44100); - REQUIRE(w1.channels() == 1); - REQUIRE(w1.basename() == "test"); - REQUIRE(w1.extension() == "wav"); - REQUIRE(w1.writeData("test-write.wav") == true); - } + wave = std::unique_ptr(new Wave(nullptr, BUFFER_SIZE, CHANNELS, + SAMPLE_RATE, BIT_DEPTH, "path/to/sample.wav")); - SECTION("test copy constructor") + REQUIRE(wave->getPath() == "path/to/sample.wav"); + REQUIRE(wave->getBasename() == "sample"); + REQUIRE(wave->getBasename(true) == "sample.wav"); + } + + SECTION("test change name") { - Wave w2(w1); - REQUIRE(w2.size == w1.size); - REQUIRE(w2.isLogical == true); - //REQUIRE(w2.rate() == 44100); // WHAT THE FUCK??? - REQUIRE(w2.channels() == 1); - REQUIRE(w2.writeData("test-write.wav") == true); + wave = std::unique_ptr(new Wave(nullptr, BUFFER_SIZE, CHANNELS, + SAMPLE_RATE, BIT_DEPTH, "path/to/sample.wav")); + wave->setName("waveform"); + + REQUIRE(wave->getPath() == "path/to/waveform.wav"); + REQUIRE(wave->getBasename() == "waveform"); + REQUIRE(wave->getBasename(true) == "waveform.wav"); } - SECTION("test rec") + SECTION("test memory cleanup") { - Wave w3; - REQUIRE(w3.allocEmpty(G_BUFFER_SIZE, G_SAMPLE_RATE) == 1); - REQUIRE(w3.rate() == G_SAMPLE_RATE); - REQUIRE(w3.channels() == 2); - REQUIRE(w3.writeData("test-write.wav") == true); + float* data = new float[BUFFER_SIZE]; + + wave = std::unique_ptr(new Wave(data, BUFFER_SIZE, CHANNELS, + SAMPLE_RATE, BIT_DEPTH, "path/to/sample.wav")); + wave->clear(); + + REQUIRE(wave->getData() == nullptr); + REQUIRE(wave->getPath() == ""); + REQUIRE(wave->getSize() == 0); } } diff --git a/tests/waveFx.cpp b/tests/waveFx.cpp new file mode 100644 index 0000000..3032078 --- /dev/null +++ b/tests/waveFx.cpp @@ -0,0 +1,143 @@ +#include +#include "../src/core/const.h" +#include "../src/core/wave.h" +#include "../src/core/waveFx.h" +#include "catch/single_include/catch.hpp" + + +using std::string; +using namespace giada::m; + + +TEST_CASE("Test waveFx") +{ + static const int SAMPLE_RATE = 44100; + static const int BUFFER_SIZE = 4000; + static const int BIT_DEPTH = 32; + + std::unique_ptr waveMono = std::unique_ptr(new Wave(new float[BUFFER_SIZE], + BUFFER_SIZE, 1, SAMPLE_RATE, BIT_DEPTH, "path/to/sample.wav")); + + std::unique_ptr waveStereo = std::unique_ptr(new Wave(new float[BUFFER_SIZE * 2], + BUFFER_SIZE, 2, SAMPLE_RATE, BIT_DEPTH, "path/to/sample.wav")); + + SECTION("test mono->stereo conversion") + { + int prevSize = waveMono->getSize(); + + REQUIRE(wfx::monoToStereo(waveMono.get()) == G_RES_OK); + REQUIRE(waveMono->getSize() == prevSize); // size does not change, channels do + REQUIRE(waveMono->getChannels() == 2); + + SECTION("test mono->stereo conversion for already stereo wave") + { + /* Should do nothing. */ + int prevSize = waveStereo->getSize(); + + REQUIRE(wfx::monoToStereo(waveStereo.get()) == G_RES_OK); + REQUIRE(waveStereo->getSize() == prevSize); + REQUIRE(waveStereo->getChannels() == 2); + } + } + + SECTION("test silence") + { + int a = 20; + int b = 57; + wfx::silence(waveStereo.get(), a, b); + + for (int i=a; igetFrame(i); + for (int k=0; kgetChannels(); k++) + REQUIRE(frame[k] == 0.0f); + } + + SECTION("test silence (mono)") + { + wfx::silence(waveMono.get(), a, b); + + for (int i=a; igetFrame(i); + for (int k=0; kgetChannels(); k++) + REQUIRE(frame[k] == 0.0f); + } + } + } + + SECTION("test cut") + { + int a = 47; + int b = 210; + int range = b - a; + int prevSize = waveStereo->getSize(); + + REQUIRE(wfx::cut(waveStereo.get(), a, b) == G_RES_OK); + REQUIRE(waveStereo->getSize() == prevSize - range); + + SECTION("test cut (mono)") + { + prevSize = waveMono->getSize(); + REQUIRE(wfx::cut(waveMono.get(), a, b) == G_RES_OK); + REQUIRE(waveMono->getSize() == prevSize - range); + } + } + + SECTION("test trim") + { + int a = 47; + int b = 210; + int area = b - a; + + REQUIRE(wfx::trim(waveStereo.get(), a, b) == G_RES_OK); + REQUIRE(waveStereo->getSize() == area); + + SECTION("test trim (mono)") + { + REQUIRE(wfx::trim(waveMono.get(), a, b) == G_RES_OK); + REQUIRE(waveMono->getSize() == area); + } + } + + SECTION("test fade") + { + int a = 47; + int b = 500; + + wfx::fade(waveStereo.get(), a, b, wfx::FADE_IN); + wfx::fade(waveStereo.get(), a, b, wfx::FADE_OUT); + + REQUIRE(waveStereo->getFrame(a)[0] == 0.0f); + REQUIRE(waveStereo->getFrame(a)[1] == 0.0f); + REQUIRE(waveStereo->getFrame(b)[0] == 0.0f); + REQUIRE(waveStereo->getFrame(b)[1] == 0.0f); + + SECTION("test fade (mono)") + { + wfx::fade(waveMono.get(), a, b, wfx::FADE_IN); + wfx::fade(waveMono.get(), a, b, wfx::FADE_OUT); + + REQUIRE(waveMono->getFrame(a)[0] == 0.0f); + REQUIRE(waveMono->getFrame(b)[0] == 0.0f); + } + } + + SECTION("test smooth") + { + int a = 11; + int b = 79; + + wfx::smooth(waveStereo.get(), a, b); + + REQUIRE(waveStereo->getFrame(a)[0] == 0.0f); + REQUIRE(waveStereo->getFrame(a)[1] == 0.0f); + REQUIRE(waveStereo->getFrame(b)[0] == 0.0f); + REQUIRE(waveStereo->getFrame(b)[1] == 0.0f); + + SECTION("test smooth (mono)") + { + wfx::smooth(waveMono.get(), a, b); + REQUIRE(waveMono->getFrame(a)[0] == 0.0f); + REQUIRE(waveMono->getFrame(b)[0] == 0.0f); + } + } +} diff --git a/tests/waveManager.cpp b/tests/waveManager.cpp new file mode 100644 index 0000000..8008959 --- /dev/null +++ b/tests/waveManager.cpp @@ -0,0 +1,67 @@ +#include +#include "../src/core/waveManager.h" +#include "../src/core/wave.h" +#include "../src/core/const.h" +#include "catch/single_include/catch.hpp" + + +using std::string; +using namespace giada::m; + + +#define G_SAMPLE_RATE 44100 +#define G_BUFFER_SIZE 4096 +#define G_CHANNELS 2 + + +TEST_CASE("Test waveManager") +{ + /* Each SECTION the TEST_CASE is executed from the start. Any code between + this comment and the first SECTION macro is exectuted before each SECTION. */ + + Wave* w; + + SECTION("test creation") + { + int res = waveManager::create("tests/resources/test.wav", &w); + std::unique_ptr wave(w); + + REQUIRE(res == G_RES_OK); + REQUIRE(wave->getRate() == G_SAMPLE_RATE); + REQUIRE(wave->getChannels() == G_CHANNELS); + REQUIRE(wave->isLogical() == false); + REQUIRE(wave->isEdited() == false); + } + + SECTION("test recording") + { + int res = waveManager::createEmpty(G_BUFFER_SIZE, G_SAMPLE_RATE, + "test.wav", &w); + std::unique_ptr wave(w); + + REQUIRE(res == G_RES_OK); + REQUIRE(wave->getRate() == G_SAMPLE_RATE); + REQUIRE(wave->getSize() == G_BUFFER_SIZE / wave->getChannels()); + REQUIRE(wave->getChannels() == G_CHANNELS); + REQUIRE(wave->isLogical() == true); + REQUIRE(wave->isEdited() == false); + } + + SECTION("test resampling") + { + int res = waveManager::create("tests/resources/test.wav", &w); + std::unique_ptr wave(w); + + REQUIRE(res == G_RES_OK); + + int oldSize = wave->getSize(); + res = waveManager::resample(wave.get(), 1, G_SAMPLE_RATE * 2); + + REQUIRE(res == G_RES_OK); + REQUIRE(wave->getRate() == G_SAMPLE_RATE * 2); + REQUIRE(wave->getSize() == oldSize * 2); + REQUIRE(wave->getChannels() == G_CHANNELS); + REQUIRE(wave->isLogical() == false); + REQUIRE(wave->isEdited() == false); + } +}